Browse Source

按用户的登录媒介区分客户端;删除旧的group及统计代码;增加session新字段:business_type;删除不再使用的配置参数;微信操作增加专门的SDK封装;重构消息推送代码,与原有的个推分离

Sand 8 years ago
parent
commit
f203a01774
35 changed files with 899 additions and 2690 deletions
  1. 22 17
      src/client/im.client.js
  2. 36 6
      src/server/endpoints/v2/session.endpoint.js
  3. 4 3
      src/server/endpoints/v2/user.endpoint.js
  4. 5 15
      src/server/handlers/socket.handler.js
  5. 16 2
      src/server/include/commons.js
  6. 150 0
      src/server/models/client/app.client.js
  7. 143 0
      src/server/models/client/wechat.client.js
  8. 0 296
      src/server/models/group.js
  9. 181 0
      src/server/models/pusher.js
  10. 2 43
      src/server/models/schedule/push.job.loader.js
  11. 4 4
      src/server/models/search.js
  12. 30 11
      src/server/models/sessions/participants.js
  13. 45 28
      src/server/models/sessions/sessions.js
  14. 5 2
      src/server/models/sessions/topics.js
  15. 0 775
      src/server/models/user/doctor.js
  16. 0 225
      src/server/models/user/patient.js
  17. 6 87
      src/server/models/user/users.js
  18. 0 148
      src/server/models/wechat.client/wechat.client.js
  19. 0 386
      src/server/public/javascripts/im.client.js
  20. 3 2
      src/server/repository/mysql/doctor.repo.js
  21. 0 117
      src/server/repository/mysql/group.msg.repo.js
  22. 0 104
      src/server/repository/mysql/group.repo.js
  23. 0 46
      src/server/repository/mysql/notify.msg.repo.js
  24. 29 5
      src/server/repository/mysql/participant.repo.js
  25. 4 2
      src/server/repository/mysql/patient.repo.js
  26. 0 292
      src/server/repository/mysql/stats.msg.repo.js
  27. 6 1
      src/server/repository/mysql/topics.repo.js
  28. 1 17
      src/server/resources/config/config.dev.js
  29. 1 17
      src/server/resources/config/config.prod.js
  30. 1 17
      src/server/resources/config/config.test.js
  31. 1 0
      src/server/resources/schema/ichat_1.2.8_table_schema.sql
  32. 5 1
      src/server/resources/schema/ichat_1.2.8_view_schema.sql
  33. 15 21
      src/server/resources/schema/temp.sql
  34. 138 0
      src/server/util/wechat.sdk.js
  35. 46 0
      test/client/im.client.session.p2p.Test.js

+ 22 - 17
src/client/im.client.js

@ -4,8 +4,11 @@
// Node.js模拟jQuery及ajax请求所需要的环境:document及XMLHttpRequest。
// 这些环境仅用于模拟,客户端在使用时候不需要真正引入
// 服务器
var imServer = "http://192.168.131.109:3008/api/v2";
if (typeof process !== 'undefined') {
    module.exports = imClient;
    var jsdom = require('jsdom').jsdom;
    var document = jsdom('<html></html>', {});
    var window = document.defaultView;
@ -17,6 +20,8 @@ if (typeof process !== 'undefined') {
    $.ajaxSettings.xhr = function () {
        return new XMLHttpRequest();
    };
    imServer = "http://localhost:3008/api/v2";
}
// 本地临时缓存Key
@ -24,9 +29,6 @@ var LocalStorageKey = {
    userId: "im_userid"
};
// 服务器
var imServer = "http://192.168.131.109:3008/api/v2";
// 资源实体在URL中的占位符
var UserPath = ":user_id";
var SessionPath = ":session_id";
@ -214,7 +216,7 @@ var imClient = {
                failure);
        },
        // 创建P2P会话
        // TEST!PASS 创建P2P会话
        createP2pSession: function (userId, peerId, success, failure) {
            httpClient.post(ENDPOINTS.Sessions.Sessions,
                {session_type: 2, session_name: "P2P", participants: [userId + ":0", peerId + ":0"]},
@ -222,7 +224,7 @@ var imClient = {
                failure);
        },
        // 获取与患者发生的会话,实际上这些是MUC会话
        // 获取与患者发生的会话
        getSessionsWithPatient: function (userId, page, size, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.Sessions,
                {user_id: userId, session_type: "1", page: page, size: size},
@ -230,7 +232,7 @@ var imClient = {
                failure);
        },
        // 获取与医生相关的会话,实际上这些是P2P,群聊和讨论组
        // 获取与医生相关的会话
        getSessionsWithDoctor: function (userId, page, size, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.Sessions,
                {user_id: userId, session_type: "2,3", page: page, size: size},
@ -254,7 +256,6 @@ var imClient = {
                failure);
        },
        // 置顶会话
        stickSession: function (userId, sessionId, success, failure) {
            httpClient.put(ENDPOINTS.Sessions.SessionSticky.replace(SessionPath, sessionId),
@ -295,7 +296,7 @@ var imClient = {
                failure);
        },
        // 咨询是否已结束
        // 咨询是否已结束, topicId为空时表示当前会话的最新消息
        isTopicEnded: function (sessionId, topicId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.TopicEnded.replace(SessionPath, sessionId).replace(TopicPath, topicId),
                {},
@ -303,7 +304,7 @@ var imClient = {
                failure);
        },
        // 发送消息,不论是何种会话,均不需要指定会话对象是谁,只需要向会话本身发送消息即可
        // TEST!PASS 发送消息,不论是何种会话,均不需要指定会话对象是谁,只需要向会话本身发送消息即可
        sendMessage: function (sessionId, userId, userName, content, contentType, success, failure) {
            httpClient.post(ENDPOINTS.Sessions.Messages.replace(SessionPath, sessionId),
                {sender_id: userId, sender_name: userName, content_type: contentType, content: content},
@ -342,7 +343,7 @@ var imClient = {
                failure);
        },
        // 获取指定会话的未读消息数
        // TEST!PASS 获取指定会话的未读消息数
        getSessionUnreadMessageCount: function (sessionId, userId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.SessionUnreadMessageCount.replace(SessionPath, sessionId),
                {user_id: userId},
@ -350,7 +351,7 @@ var imClient = {
                failure);
        },
        // 获取指定会话的未读消息
        // TEST!PASS 获取指定会话的未读消息
        getSessionUnreadMessages: function (sessionId, userId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.SessionUnreadMessages.replace(SessionPath, sessionId),
                {user_id: userId},
@ -366,9 +367,9 @@ var imClient = {
                failure);
        },
        // 获取会话成员列表
        // TEST!PASS 获取会话成员列表
        getParticipants: function (sessionId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.SessionParticipants.replace(SessionPath, sessionId),
            httpClient.get(ENDPOINTS.Sessions.Participants.replace(SessionPath, sessionId),
                {},
                success,
                failure);
@ -382,9 +383,9 @@ var imClient = {
                failure);
        },
        // 获取会话成员头像
        getParticipantAvatar: function (sessionId, participantId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.ParticipantAvatar.replace(SessionPath, sessionId).replace(ParticipantPath, participantId),
        // TEST!PASS 获取会话成员头像
        getParticipantsAvatars: function (sessionId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.ParticipantsAvatar.replace(SessionPath, sessionId),
                {},
                success,
                failure);
@ -392,3 +393,7 @@ var imClient = {
    },
    Search: {}
};
if (typeof process !== 'undefined') {
    module.exports = imClient;
}

+ 36 - 6
src/server/endpoints/v2/session.endpoint.js

@ -119,37 +119,65 @@ router.delete(APIv2.Sessions.SessionSticky, function (req, res) {
    if (!sessionId) {
        throw {httpStatus: 406, message: 'Missing sessionId.'};
    }
    let sessions = new Sessions();
    ControllerUtil.regModelEventHandler(sessions, res);
    sessions.cancelStickSession(sessionId, user);
});
/**
 * 移除成员
 * 获取会话成员。
 */
router.get(APIv2.Sessions.Participants, function (req, res) {
    let sessionId = req.params.session_id;
    let participants = new Participants();
    ControllerUtil.regModelEventHandler(participants, res);
    participants.getParticipants(sessionId);
});
/**
 * 获取会话成员头像列表。
 */
router.get(APIv2.Sessions.ParticipantsAvatar, function (req, res) {
    let sessionId = req.params.session_id;
    let participants = new Participants();
    ControllerUtil.regModelEventHandler(participants, res);
    participants.getParticipantsAvatar(sessionId);
});
/**
 * 增加成员
 * user:移除的人员
 * sessionId 会话ID
 */
router.delete(APIv2.Sessions.Participant, function (req, res) {
router.put(APIv2.Sessions.Participant, function (req, res) {
    let user = req.query.user;
    if (!user) {
        throw {httpStatus: 406, message: 'Missing user.'};
    }
    let sessionId = req.query.sessionId;
    if (!sessionId) {
        throw {httpStatus: 406, message: 'Missing sessionId.'};
    }
    let participants = new Participants();
    ControllerUtil.regModelEventHandler(sessions, res);
    participants.deleteUser(sessionId, user);
    participants.addUser(sessionId, user);
});
/**
 * 增加成员
 * 移除成员
 * user:移除的人员
 * sessionId 会话ID
 */
router.put(APIv2.Sessions.Participant, function (req, res) {
router.delete(APIv2.Sessions.Participant, function (req, res) {
    let user = req.query.user;
    if (!user) {
        throw {httpStatus: 406, message: 'Missing user.'};
@ -158,9 +186,11 @@ router.put(APIv2.Sessions.Participant, function (req, res) {
    if (!sessionId) {
        throw {httpStatus: 406, message: 'Missing sessionId.'};
    }
    let participants = new Participants();
    ControllerUtil.regModelEventHandler(sessions, res);
    participants.pushUser(sessionId, user);
    participants.deleteUser(sessionId, user);
});
/**

+ 4 - 3
src/server/endpoints/v2/user.endpoint.js

@ -6,6 +6,7 @@
let ObjectUtil = require('../../util/object.util');
let ControllerUtil = require('../../util/controller.util');
let Users = require('../../models/user/users');
let AppClient = require('../../models/client/app.client');
let express = require('express');
let router = express.Router();
@ -62,10 +63,10 @@ router.put(APIv2.Users.UserStatus, function (req, res) {
        throw {httpStatus: 406, message: 'Validation Failed. Missing field(s): user_id.'};
    }
    let users = new Users();
    ControllerUtil.regModelEventHandler(users, res);
    let appClient = new AppClient();
    ControllerUtil.regModelEventHandler(appClient, res);
    users.updateAppStatus(userId, payload.app_in_bg);
    appClient.updateAppStatus(userId, payload.app_in_bg);
});
/**

+ 5 - 15
src/server/handlers/socket.handler.js

@ -9,11 +9,8 @@ let log = require("../util/log.js");
let clientCache = require('../models/socket.io/client.cache').clientCache();
let PatientClient = require('./../models/socket.io/patient.client');
let Doctor = require('../models/user/doctor');
let doctor = new Doctor();
let Group = require('../models/group');
let group = new Group();
let Sessions = require('../models/sessions/sessions');
let sessions = new Sessions();
class SocketHandler {
    constructor(socketServer) {
@ -22,8 +19,6 @@ class SocketHandler {
    /**
     * 启用事件监听。
     *
     * @param socket
     */
    start() {
        let socketServer = this._socketServer;
@ -47,16 +42,11 @@ class SocketHandler {
            // 接收客户端消息
            socket.on('message', function (data) {
                log.info('Got message from ' + clientCache.findBySocket(socket).userId);
                log.debug('Got message from ' + clientCache.findBySocket(socket).userId);
                let targetType = data.targetType;
                let sessionId = data.session_id;
                let message = data.message;
                if (targetType == 1) {
                    doctor.sendMessage(message);
                } else {
                    group.sendMessage(message);
                }
                sessions.saveMessageBySession(sessionId, message);
            });
            // 客户端退出

+ 16 - 2
src/server/include/commons.js

@ -68,16 +68,30 @@ exports.PARTICIPANT_ROLES = PARTICIPANT_ROLES;
/**
 *  消息内容类型。
 */
exports.CONTENT_TYPE = {
const CONTENT_TYPE = {
    PlainText: 1,   // 信息
    Image: 2,       // 图片信息
    Audio: 3,       // 语音信息
    Article: 4,     // 文章信息
    GoTo: 5,        // 跳转信息
    TopicBegin: 6,  // 议题开始
    TopicEnd: 7     // 议题结束
    TopicEnd: 7,    // 议题结束
    typeToDescription: function (type, defaultDescription) {
        if (type === CONTENT_TYPES.Image) {
            return '[图片]';
        } else if (type === CONTENT_TYPES.Audio) {
            return '[语音]';
        } else if (type > 3) {
            return defaultDescription;
        }
        return false;
    }
};
exports.CONTENT_TYPE = CONTENT_TYPE;
/**
 * 客户端平台。
 */

+ 150 - 0
src/server/models/client/app.client.js

@ -0,0 +1,150 @@
/**
 * App客户端。
 */
"use strict";
let RedisClient = require('../../repository/redis/redis.client');
let RedisModel = require('./../redis.model');
let AppStatusRepo = require('../../repository/mysql/app.status.repo');
let ObjectUtil = require("../../util/object.util.js");
let ModelUtil = require('../../util/model.util');
let log = require("../../util/log.js");
let pusher = require('../pusher');
let redisConn = RedisClient.redisClient().connection;
const CONTENT_TYPES = require('../../include/commons').CONTENT_TYPE;
const REDIS_KEYS = require('../../include/commons').REDIS_KEYS;
const PLATFORMS = require('../../include/commons').PLATFORM;
class AppClient extends RedisModel {
    constructor() {
        super();
    }
    /**
     * 更新客户端App状态。
     *
     * @param userId
     * @param appInBg
     * @param handler
     */
    updateAppStatus(userId, appInBg, handler) {
        let self = this;
        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) {
                    if (handler) {
                        handler(null, true);
                    } else {
                        ModelUtil.emitOK(self.eventEmitter, {});
                    }
                });
            } else {
                if (handler) {
                    handler(null, true);
                } else {
                    ModelUtil.emitDataNotFound(self.eventEmitter, {"message": "User is offline, unable to update app status."});
                }
            }
        });
    }
    /**
     * 获取用户App端状态。若Redis中找不到,则从MySQL中查找。
     *
     * @param userId
     * @param handler
     */
    static getAppStatus(userId, handler) {
        let userStatusKey = RedisModel.makeRedisKey(REDIS_KEYS.UserStatus, userId);
        redisConn.hgetallAsync(userStatusKey)
            .then(function (res) {
                if (res != null) {
                    handler(null, res);
                } else {
                    AppStatusRepo.findOne(userId, function (err, res) {
                        if (err) throw err;
                        let userStatus = null;
                        if (res.length > 0) {
                            userStatus = {
                                platform: res[0].platform,
                                token: res[0].token,
                                client_id: res[0].client_id,
                                app_in_bg: res[0].app_in_bg,
                                last_login_time: res[0].last_login_time
                            }
                        }
                        handler(null, userStatus);
                    });
                }
            })
            .catch(function (err) {
                handler(err, null);
            });
    }
    /**
     * 向App端推送消息。
     *
     * @param message
     * @param sessionType
     */
    static sendNotification(message, sessionType) {
        AppClient.getAppStatus(message.to, function (err, userStatus) {
            if (err) {
                ModelUtil.logError("Get user app status failed", err);
                return;
            }
            let tipMessage = CONTENT_TYPES.typeToDescription(message.content_type, "您有一条新消息") || message.content;
            let customData = {
                session_id: '' || message.session_id,
                session_type: sessionType,
                topic_id: '' || message.topic_id,
                from: '' || message.from,
                data: message.content
            };
            AppClient.getAppStatus(userId, function (err, userStatus) {
                if (userStatus.platform === PLATFORMS.iOS) {
                    pusher.pushToSingleViaAPN(tipMessage, customData, message.contentType, userStatus.token, handler);
                } else if (userStatus.platform === PLATFORMS.Android) {
                    let title = '新消息';
                    pusher.pushToSingleViaAndroid(title, tipMessage, customData, userStatus.client_id, userStatus.app_in_bg, handler);                }
            });
        });
    }
    /**
     * 将消息的返回结果合并成JSON。
     *
     * @param rows
     */
    static fillMessages(rows) {
        let messages = {startId: rows.length > 0 ? rows[0].msg_id : '', count: rows.length, records: []};
        for (let i = 0; i < rows.length; i++) {
            let row = rows[i];
            let record = {
                id: row.msg_id,
                from: row.from_uid,
                contentType: row.type,
                content: row.content,
                timestamp: ObjectUtil.timestampToLong(row.timestamp)
            };
            if (row.to_uid !== undefined) record.to = row.to_uid;
            if (row.at_uid !== undefined) record.at = row.at_uid;
            messages.records.push(record);
        }
        return messages;
    }
}
// Expose class
module.exports = AppClient;

+ 143 - 0
src/server/models/client/wechat.client.js

@ -0,0 +1,143 @@
/**
 * 微信客户端。
 */
"use strict";
let RedisClient = require('../../repository/redis/redis.client');
let RedisModel = require('../redis.model');
let ObjectUtil = require("../../util/object.util.js");
let ModelUtil = require('../../util/model.util');
let DoctorRepo = require('../../repository/mysql/doctor.repo');
let WechatSDK = require('../../util/wechat.sdk');
let PatientRepo = require('../../repository/mysql/patient.repo');
let redisConn = RedisClient.redisClient().connection;
let clientCache = require('../socket.io/client.cache').clientCache();
let configFile = require('../../include/commons').CONFIG_FILE;
let config = require('../../resources/config/' + configFile);
let log = require("../../util/log.js");
let https = require('https');
const CONTENT_TYPES = require('../../include/commons').CONTENT_TYPE;
const REDIS_KEYS = require('../../include/commons').REDIS_KEYS;
class WechatClient extends RedisModel {
    constructor() {
        super();
    }
    /**
     * 取得用户微信端状态。若Redis中找不到,则从MySQL中查找。
     *
     * @param userId
     * @param handler
     */
    static getWechatStatus(userId, handler) {
        redisConn.hgetallAsync(RedisModel.makeRedisKey(REDIS_KEYS.UserWechatStatus, userId))
            .then(function (status) {
                if (status == null) {
                    PatientRepo.findWechatOpenId(userId, handler);
                } else {
                    handler(null, {open_id: status.open_id});
                }
            })
            .catch(function (err) {
                handler(err, null);
            });
    }
    /**
     * 向微信端用户发送消息。若用户微信端在线,通过Web Socket推送给患者,如果不在线则通过微信的模板消息。
     *
     * @param message 消息
     */
    sendMessage(message) {
        let patientClient = clientCache.findById(message.to);
        if (patientClient) {
            this.sendViaWebSocket(patientClient.socket, message);
        } else {
            log.info("User is not online, user id: ", message.to, ", sending via wechat template message.");
            this.sendViaTemplateMessage(message);
        }
    };
    sendViaWebSocket(socket, message){
        message.timestamp = ObjectUtil.timestampToLong(message.timestamp);
        socket.emit('message', message);
    }
    /**
     * 发送微信模板消息给居民
     *
     * @param message
     */
    sendViaTemplateMessage(message) {
        function sendWxMessage(openid, name, topic) {
            var replyContent = message.content;
            switch (Number.parseInt(message.contentType)) {
                case CONTENT_TYPES.Image:
                    replyContent = "[图片]";
                    break;
                case CONTENT_TYPES.Audio:
                    replyContent = "[语音]";
                    break;
                case 0:
                case CONTENT_TYPES.Article:
                case CONTENT_TYPES.GoTo:
                case CONTENT_TYPES.TopicBegin:
                case CONTENT_TYPES.TopicEnd:
                    return;
                default:
                    break;
            }
            // 发送模板消息
            WechatSDK.sendTemplateMessage({
                touser: openid,
                template_id: config.wechatConfig.template.consultTemplate,
                url: config.wechatConfig.baseUrl + "/wx/html/yszx/html/consulting-doctor.html?openid=" + openid +
                "&consult=" + topic.name + "&toUser=" + message.to,
                data: {
                    first: {value: "您的健康咨询有新的回复", color: "#000000"}
                    , remark: {value: "", color: "#000000"}
                    , keyword1: {value: topic.description, color: "#000000"}
                    , keyword2: {value: replyContent, color: "#000000"}
                    , keyword3: {value: name, color: "#000000"}
                }
            });
        }
        // 查询微信OpenId及医生信息,用于构建微信模板消息
        PatientRepo.findWechatOpenId(message.to, function (err, result) {
            if (err) {
                ModelUtil.logError("Get wechat openid failed", err);
                return;
            }
            var openid = result && result.length > 0 ? result[0].openid : "";
            if (openid) {
                DoctorRepo.findOne(message.from, function (err, result) {
                    if (err) {
                        ModelUtil.logError("Get doctor info failed", err);
                        return;
                    }
                    if (result && result.length > 0) {
                        var name = result[0].name;
                        var topic = result && result.length > 0 ? result[0] : "";
                        if (topic) {
                            sendWxMessage(openid, name, topic);
                        }
                    } else {
                        ModelUtil.logError("Can not find user info", err);
                    }
                });
            } else {
                ModelUtil.logError("User does not bind with wechat, user id: " + message.to, err);
            }
        });
    };
}
module.exports = WechatClient;

+ 0 - 296
src/server/models/group.js

@ -1,296 +0,0 @@
/**
 * 聊天组模型。
 */
"use strict";
let BaseModel = require('./base.model');
let log = require("../util/log.js");
let ModelUtil = require('../util/model.util');
//let getui = require('getui');
let Patient = new require("../models/user/patient");
let Doctor = new require('../models/user/doctor');
let GroupRepo = require('../repository/mysql/group.repo');
let GroupMsgRepo = require('../repository/mysql/group.msg.repo');
let StatsRepo = require("../repository/mysql/stats.msg.repo");
let ObjectUtil = require("../util/object.util.js");
const GROUP_TYPES = require('../include/commons').GROUP_TYPE;
const MAX_INT = require('../include/commons').MAX_INT;
class GroupMessage extends BaseModel {
    constructor() {
        super();
    }
    /**
     * 发送消息。
     *
     * @param message
     */
    sendMessage(message) {
        let self = this;
        GroupRepo.isGroupMember(message.group, message.groupType, message.from, function (err, result) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Check group member failed', err);
                return;
            }
            if (result.length == 0) {
                ModelUtil.emitDataNotFound(self.eventEmitter, 'Member with id "' + message.from + '" is not in group "' + message.group + '"');
                return;
            }
            // 保存群组消息
            GroupMsgRepo.save(message.from, message.group, message.at, message.contentType, message.content, function (err, insertedRow) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, 'Save group message failed', err);
                    return;
                }
                GroupMsgRepo.findOneMessage(insertedRow.insertId, function (err, groupMsg) {
                    if (err) {
                        ModelUtil.emitError(self.eventEmitter, 'Save group message success, but return this message failed', err);
                        return;
                    }
                    // 关闭网络连接后执行后续操作
                    let feedback = Doctor.fillMessages(groupMsg);
                    ModelUtil.emitOK(self.eventEmitter, feedback);
                    // 推送通知消息给群组成员
                    GroupRepo.getMembers(message.group, message.groupType, function (err, members) {
                        if (err) {
                            log.error('Get group members failed: ', err);
                            return;
                        }
                        if (members.length == 0) {
                            log.warn('No members in group ', message.group,
                                message.groupType === GROUP_TYPES.AdminTeam ? " of admin team." : "of discussion group.");
                            return;
                        }
                        // 逐个推送通知,患者与医生推送方式不一样
                        for (let i = 0; i < members.length; i++) {
                            let member = members[i];
                            if (member.user_id === message.from) continue;
                            (function (userId) {
                                Patient.isPatientCode(userId,
                                    function () {
                                        let msg = {
                                            from: message.from,
                                            to: userId,
                                            contentType: message.contentType,
                                            content: message.content,
                                            msgId: groupMsg[0].msg_id,
                                            group: message.group
                                        };
                                        new Patient().pushGroupMessage(msg);
                                    },
                                    function () {
                                        let msg = {
                                            from: message.from,
                                            to: userId,
                                            contentType: message.contentType,
                                            content: message.content,
                                            msgId: groupMsg[0].msg_id,
                                            group: message.group
                                        };
                                        Doctor.pushMessage(msg, 'group_msg');
                                        // 更新用户组内消息摘要
                                        let at = message.at == userId ? 1 : 0;
                                        StatsRepo.updateGroupChatSummary(userId,
                                            message.group,
                                            message.from,
                                            at,
                                            message.contentType,
                                            message.content,
                                            true,
                                            function (err, result) {
                                                if (err) log.error(err);
                                            });
                                    });
                            })(member.user_id);
                        }
                    });
                });
                // 更新组内统计信息
                StatsRepo.updateGroupChatSummary(message.from, message.group, message.from, 0, message.contentType, message.content, false, function (err, result) {
                    if (err) log.error(err);
                });
            });
        });
    }
    /**
     * 获取组消息。
     */
    getMessages(groupId, memberId, contentType, msgStartId, msgEndId, count) {
        let self = this;
        GroupMsgRepo.findAllMessages(groupId, !contentType ? "1,2,3,5,6" : contentType, msgStartId, msgEndId, count, function (err, rows) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Get group message failed', err);
                return;
            }
            // 先给客户端返回数据
            let messages = Doctor.fillMessages(rows);
            ModelUtil.emitOK(self.eventEmitter, messages);
            // 清空统计信息
            StatsRepo.clearGroupChatSummary(memberId, groupId, function (err, result) {
                if (err) console.log(err);
            });
        });
    }
    /**
     * 获取组内未读消息。
     * @param groupId
     */
    getUnreadMessages(groupId, memberId) {
        let self = this;
        StatsRepo.getGroupChatSummary(memberId, groupId, function (err, summary) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, "Get unread group messages failed", err);
                    return;
                }
                let messages = {startId: 0, count: 0, records: []};
                if (summary.length == 0 || summary[0].new_msg_count === 0) {
                    ModelUtil.emitOK(messages);
                    return;
                }
                messages.count = summary[0].new_msg_count;
                GroupMsgRepo.findUnread(groupId, MAX_INT, messages.count, function (err, rows) {
                    if (err) {
                        ModelUtil.emitError(self.eventEmitter, "Get unread group messages failed", err);
                        return;
                    }
                    let feedback = Doctor.fillMessages(rows);
                    ModelUtil.emitOK(feedback);
                });
            }
        )
        ;
    }
    /**
     * 获取未读消息数量。
     *
     * @param memberId
     */
    getUnreadMessageCount(memberId) {
        let self = this;
        StatsRepo.getGroupChatAllUnReadCount(memberId, function (err, result) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Get all unread messages failed', err);
                return;
            }
            let data = {
                userId: memberId,
                messageType: 2,
                newMessageCount: 0
            };
            if (result.length > 0) {
                for (let index = 0; index < result.length; index++) {
                    data.newMessageCount += result[index].new_msg_count;
                }
            }
            ModelUtil.emitOK(self.eventEmitter, data);
        });
    }
    /**
     * 获取特定群组消息统计情况.
     *
     * @param groupId
     * @param memberId
     */
    getChatSummary(groupId, memberId) {
        let self = this;
        StatsRepo.getGroupChatSummary(memberId, groupId, function (err, result) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Get group stats failed', err);
                return;
            }
            let data = {
                userId: userId,
                from: "",
                groupId: groupId,
                atMe: 0,
                lastContentType: 1,
                lastContent: "",
                newMessageCount: 0,
                timestamp: 0
            };
            if (result.length > 0) {
                let row = result[0];
                data.from = row.from_uid;
                data.groupId = row.gid;
                data.atMe = row.at_me;
                data.lastContentType = row.last_content_type;
                data.lastContent = row.lastContent;
                data.newMessageCount = row.new_msg_count;
                data.timestamp = ObjectUtil.timestampToLong(row.timestamp)
            }
            ModelUtil.emitOK(self.eventEmitter, data);
        });
    }
    /**
     * 获取成员头像。
     *
     * @param groups 要获取的组ID列表。
     */
    getMemberAvatars(groups) {
        let self = this;
        let avatars = [];
        GroupRepo.getMembersAvatar(groups, function (err, members) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, "Get group member's avatar list failed", err);
                return;
            }
            let lastGroup;
            let lastGroupCode = '';
            for (let i = 0; i < members.length; ++i) {
                let member = members[i];
                let groupCode = member.g_code;
                if (lastGroupCode !== groupCode) {
                    lastGroupCode = groupCode;
                    lastGroup = {groupCode: groupCode, avatars: []};
                    if (lastGroupCode !== '') {
                        avatars.push(lastGroup);
                    }
                }
                lastGroup.avatars.push({
                    code: member.code,
                    name: member.name,
                    avatar: member.photo === null ? "" : member.photo,
                    role: member.type
                });
            }
            ModelUtil.emitOK(self.eventEmitter, avatars);
        });
    }
}
module.exports = GroupMessage;

+ 181 - 0
src/server/models/pusher.js

@ -0,0 +1,181 @@
/**
 * 消息推送服务工具,包装消息推送方法。
 *
 * 当前使用个推的推送服务。
 */
"use strict";
let RedisModel = require('./redis.model');
let GeTui = require('getui/GT.push');
let Target = require('getui/getui/Target');
let APNTemplate = require('getui/getui/template/APNTemplate');
let BaseTemplate = require('getui/getui/template/BaseTemplate');
let APNPayload = require('getui/payload/APNPayload');
let SimpleAlertMsg = require('getui/payload/SimpleAlertMsg');
let NotyPopLoadTemplate = require('getui/getui/template/NotyPopLoadTemplate');
let LinkTemplate = require('getui/getui/template/LinkTemplate');
let NotificationTemplate = require('getui/getui/template/NotificationTemplate');
let TransmissionTemplate = require('getui/getui/template/TransmissionTemplate');
let SingleMessage = require('getui/getui/message/SingleMessage');
let AppMessage = require('getui/getui/message/AppMessage');
let log = require("../util/log.js");
let configFile = require('../include/commons').CONFIG_FILE;
let config = require('../resources/config/' + configFile);
class Pusher extends RedisModel {
    constructor() {
        super();
        this.getuiConfig = config.geTuiAppStoreCfg;
        this.gt = new GeTui(this.getuiConfig.HOST, this.getuiConfig.APPKEY, this.getuiConfig.MASTERSECRET);
    }
    /**
     * iOS单个推送。使用APN简单推送。
     *
     * @param message
     * @param customData
     * @param type
     * @param deviceToken
     * @param handler
     */
    pushToSingleViaAPN(message, customData, type, deviceToken, handler) {
        try {
            var simpleAlertMsg = new SimpleAlertMsg();
            simpleAlertMsg.alertMsg = message;
            var payload = new APNPayload();
            payload.alertMsg = simpleAlertMsg;
            payload.badge = 0;
            payload.contentAvailable = 1;
            payload.category = type;
            payload.customMsg.payload1 = customData;
            var template = new APNTemplate();
            template.setApnInfo(payload);
            var singleMessage = new SingleMessage();
            singleMessage.setData(template);
            this.gt.pushAPNMessageToSingle(this.getuiConfig.APPID, deviceToken, singleMessage, function (err, res) {
                if (err) {
                    log.error('Push via APN failed:' + e);
                } else {
                    handler(null, res);
                }
            });
        } catch (e) {
            log.error('Push via APN failed:' + e);
        }
    }
    /**
     * 安卓单个推送。
     *
     * @param title
     * @param message
     * @param customData
     * @param clientid
     * @param appInBg
     * @param handler
     */
    pushToSingleViaAndroid(title, message, customData, clientid, appInBg, handler) {
        if (appInBg) {
            this._pushAndroidNotify(clientid, title, message, customData, handler);
        } else {
            this._pushAndroidTransmission(clientid, customData, handler);
        }
    }
    _pushAndroidNotify(clientid, title, msg, data, handler) {
        var transmissionContent = {
            pushtype: 'notify',
            data: data
        };
        var template = new NotificationTemplate({
            appId: this.getuiConfig.APPID,
            appKey: this.getuiConfig.APPKEY,
            title: title,
            text: msg,
            logo: '',
            isRing: true,
            isVibrate: true,
            isClearable: true,
            transmissionType: 1,
            transmissionContent: JSON.stringify(transmissionContent)
        });
        //个推信息体
        var message = new SingleMessage({
            isOffline: true,                        //是否离线
            offlineExpireTime: 3600 * 12 * 1000,    //离线时间
            data: template,                          //设置推送消息类型
            pushNetWorkType: 0                     //是否wifi ,0不限,1wifi
        });
        //接收方
        var target = new Target({
            appId: this.getuiConfig.APPID,
            clientId: clientid
        });
        this.gt.pushMessageToSingle(message, target, function (err, res) {
            if (err != null && err.exception != null && err.exception instanceof RequestError) {
                var requestId = err.exception.requestId;
                console.log(err.exception.requestId);
                this.gt.pushMessageToSingle(message, target, requestId, function (err, res) {
                    handler(err, res);
                });
            } else {
                handler(err, res);
            }
        });
    }
    _pushAndroidTransmission(clientid, customData, handler) {
        var transmissionContent = {
            pushtype: 'transmission',
            data: customData
        };
        var template = new TransmissionTemplate({
            appId: this.getuiConfig.APPID,
            appKey: this.getuiConfig.APPKEY,
            transmissionType: 2,
            transmissionContent: JSON.stringify(transmissionContent)
        });
        // 个推信息实体
        var message = new SingleMessage({
            isOffline: true,                            //是否离线
            offlineExpireTime: 3600 * 12 * 1000,       //离线时间
            data: template,                             //设置推送消息类型
            pushNetWorkType: 0                          //是否wifi ,0不限,1 wifi
        });
        // 接收方
        var target = new Target({
            appId: this.getuiConfig.APPID,
            clientId: clientid
        });
        this.gt.pushMessageToSingle(message, target, function (err, res) {
            if (err != null && err.exception != null && err.exception instanceof RequestError) {
                var requestId = err.exception.requestId;
                log.info("Push android single failed without rquestId, retry with requestId: ", err.exception.requestId);
                this.gt.pushMessageToSingle(message, target, requestId, function (err, res) {
                    if (err) {
                        log.error("Push android single via transmission with requestId failed: ", err);
                    }
                });
            }
        });
    }
}
let pusher = new Pusher();
module.exports = pusher;

+ 2 - 43
src/server/models/schedule/push.job.loader.js

@ -7,55 +7,14 @@
"use strict";
let Schedule = require('./schedule');
let Doctor = require("../user/doctor.js");
const nmRepo = require('../../repository/mysql/notify.msg.repo');
const doctorRepo = require('../../repository/mysql/doctor.repo');
const log = require("../../util/log.js");
let log = require("../../util/log.js");
let doctorRepo = require('../../repository/mysql/doctor.repo');
class PushJobLoader{
    constructor(){}
    static load(){
        nmRepo.findUnpushedMessages(function (err, rows) {
            if(err){
                log.error('Load schedule jobs failed: ', err);
                return;
            }
            for(let i = 0; i < rows.length; ++i){
                let row = rows[i];
                let delay = new Date(Date.parse(row.delay));
                let userId = row.to_uid;
                let title = row.title;
                let contentType = row.type;
                let content = row.content;
                let notifyMessage = row.data;
                Schedule.dateSchedule(delay, function (userId, title, contentType, content, notifyMessage) {
                    doctorRepo.getUserStatus(userId, function (err, result) {
                        if(err) {
                            log.error('Get user status failed in schedule: ', err);
                            return;
                        }
                        if(result.length > 0){
                            let userStatus = result[0];
                            Doctor.pushToClient(userId, userStatus.client_id, userStatus.status, userStatus.token, contentType,
                                title, content, notifyMessage, userStatus.platform, function (err, result) {
                                    if (err != null) {
                                        log.error(err);
                                    } else {
                                        log.info(result);
                                    }
                                });
                        } else {
                            log.warn('User is not online, scheduled pushing job omitted.');
                        }
                    })
                }.bind(null, userId, title, contentType, content, notifyMessage));
            }
        });
    }
}

+ 4 - 4
src/server/models/search.js

@ -10,10 +10,10 @@
 */
"use strict";
let BaseModel = require('./base.model');
let searchRepo = require('../repository/mysql/search.repo');
let modelUtil = require("../util/model.util");
let objectUtil = require('../util/object.util');
let BaseModel = require('../base.model');
let searchRepo = require('../../repository/mysql/search.repo');
let modelUtil = require("../../util/model.util");
let objectUtil = require('../../util/object.util');
class Search extends BaseModel {
    constructor() {

+ 30 - 11
src/server/models/sessions/participants.js

@ -19,20 +19,39 @@ class Participants extends RedisModel {
    }
    /**
     * 获取会话的成员列表
     * 获取会话的成员列表,直接从MySQL获取。
     *
     * @param sessionId
     * @param handler
     */
    getParticipants(sessionId, handler) {
        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) {
                    handler(res);
                })
            } else {
                ParticipantRepo.findParticipants(sessionId, handler);
    getParticipants(sessionId) {
        let self = this;
        ParticipantRepo.findAll(sessionId, function (err, participants) {
            if(err){
                ModelUtil.emitError(self.eventEmitter, "Get session participants error", err);
                return;
            }
            ModelUtil.emitOK(self.eventEmitter, participants);
        });
    }
    /**
     * 获取所有成员的头像。
     *
     * @param sessionId
     */
    getParticipantsAvatar(sessionId){
        let self = this;
        ParticipantRepo.findAllAvatars(sessionId, function (err, participantsAvatars) {
            if(err){
                ModelUtil.emitError(self.eventEmitter, "Get session participant's avatars error", err);
                return;
            }
            ModelUtil.emitOK(self.eventEmitter, participantsAvatars);
        })
    }
@ -170,7 +189,7 @@ class Participants extends RedisModel {
     * @param sessionId
     * @param user
     */
    pushUser(sessionId, user) {
    addUser(sessionId, user) {
        let self = this;
        let users = [];
        users.push(user);

+ 45 - 28
src/server/models/sessions/sessions.js

@ -11,15 +11,17 @@ let Users = require('../user/users');
let Participants = require('./Participants');
let SessionRepo = require('../../repository/mysql/session.repo');
let ParticipantRepo = require('../../repository/mysql/participant.repo');
let WechatClient = require("../client/wechat.client.js");
let AppClient = require("../client/app.client.js");
let configFile = require('../../include/commons').CONFIG_FILE;
let config = require('../../resources/config/' + configFile);
let redis = RedisClient.redisClient().connection;
let logger = require('../../util/log.js');
let mongoose = require('mongoose');
var async = require("async");
var ObjectUtil = require("../../util/object.util.js");
let MessageRepo = require('../../repository/mysql/message.repo');
let async = require("async");
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;
@ -348,7 +350,7 @@ class Sessions extends RedisModel {
                        let startMsgScore = res[1];
                        let endMsgScore = res[0];
                        if(startMsgScore == null || endMsgScore == null){
                        if (startMsgScore == null || endMsgScore == null) {
                            handler(null, []);
                            return;
                        }
@ -444,7 +446,7 @@ class Sessions extends RedisModel {
            },
            // 获取消息
            function (messageIds, callback) {
                if(messageIds.length == 0){
                if (messageIds.length == 0) {
                    ModelUtil.emitOK(self.eventEmitter, []);
                    return;
                }
@ -452,7 +454,7 @@ class Sessions extends RedisModel {
                let startMsgId = messageIds[0];
                let endMsgId = messageIds[messageIds.length - 1];
                self.getMessagesByPage(sessionId, userId, startMsgId, endMsgId, 0, messageIds.length, function (err, res) {
                    if(err){
                    if (err) {
                        ModelUtil.emitError(self.eventEmitter, err.message);
                        return;
                    }
@ -502,7 +504,7 @@ class Sessions extends RedisModel {
                    }
                    messages.saveMessageToRedis(sessionId, sessionType, messageId, message);
                    Sessions.updateParticipantLastFetchTime(sessionId,message.sender_id,message.timestamp.getTime());
                    Sessions.updateParticipantLastFetchTime(sessionId, message.sender_id, message.timestamp.getTime());
                    messages.saveMessageToMysql(sessionId, sessionType, messageId, message, function (err, res) {
                        if (err) {
                            ModelUtil.emitError(self.eventEmitter, {message: "Failed to save message to mysql: " + err});
@ -527,44 +529,43 @@ class Sessions extends RedisModel {
     *
     * @param message
     * @param sessionId
     * @param handler
     */
    saveMessageByTopic(message, sessionId, handler) {
        let self = this;
        let messages = new Messages();
        let participants = new Participants();
        let session_key = RedisModel.makeRedisKey(REDIS_KEYS.Session, sessionId);
        let messageId = mongoose.Types.ObjectId().toString();
        let sessionType = 0;
        let name = "";
        let sessionName = "";
        message.id = messageId;
        // 发送成员必须处于会话中
        participants.existsParticipant(sessionId, message.sender_id, function (err, res) {
            //校验发送成员是都在讨论组
            if (res) {
                redis.hmgetAsync(session_key, ["type", "name"]).then(function (res) {
                    sessionType = res[0];
                    name = res[1];
                    if (!sessionType || !name) {
                    sessionName = res[1];
                    if (!sessionType || !sessionName) {
                        logger.error("session is error for key " + session_key);
                        throw "session is not found";
                    }
                }).then(function (res) {
                    //更新消息相关
                    return messages.saveMessageToRedis(sessionId, sessionType, messageId, message);
                }).then(function (res) {
                    //更新最后一次消息获取时间
                    Sessions.updateParticipantLastFetchTime(sessionId,message.sender_id,message.timestamp.getTime());
                    //更新session的最后一条聊天记录
                    return Messages.updateLastContent(session_key, sessionType, name, message);
                    // 更新消息存储
                    messages.saveMessageToRedis(sessionId, sessionType, messageId, message);
                    messages.saveMessageToMysql(sessionId, sessionType, messageId, message);
                    // 更新会话最新状态及成员最后一次消息获取时间
                    Sessions.updateParticipantLastFetchTime(sessionId, message.sender_id, message.timestamp.getTime());
                    Messages.updateLastContent(session_key, sessionType, sessionName, message);
                    handler(null, messageId);
                }).then(function (res) {
                    //操作mysql数据库
                    messages.saveMessageToMysql(sessionId, sessionType, messageId, message);
                    //返回数据给前端。
                    handler(null, messageId)
                    //消息推送
                }).catch(function (res) {
                    handler(res, messageId)
                    // TODO: 消息推送
                }).catch(function (err) {
                    handler(err, messageId)
                })
            } else {
                handler("用户不在此会话当中!", messageId);
@ -632,8 +633,7 @@ class Sessions extends RedisModel {
     * @param sessionId
     * @param userId
     */
    static updateParticipantLastFetchTime(sessionId, userId,score) {
        score = score+1;
    static updateParticipantLastFetchTime(sessionId, userId, score) {
        let participantsKey = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipants, sessionId);
        redis.zaddAsync(participantsKey, score, userId)
            .then(function (res) {
@ -644,6 +644,23 @@ class Sessions extends RedisModel {
                logger.error("Update participant last fetch time error: ", err);
            });
    }
    /**
     * 向用户推送通知,微信端用户直接推送消息,APP端通过个推发送通知消息。
     *
     * @param targetUserId
     * @param message
     */
    static pushNotification(targetUserId, message) {
        Users.isPatientId(targetUserId, function (isPatient) {
            if (isPatient) {
                WechatClient.sendMessage(message);
            }
            else {
                AppClient.sendMessage(message);
            }
        });
    }
}
// Expose class

+ 5 - 2
src/server/models/sessions/topics.js

@ -112,7 +112,7 @@ class Topics extends RedisModel {
            msg.sender_id = messages.senderId;
            msg.sender_name = messages.senderName;
            msg.content_type = 6;
            msg.content = "开始咨询"
            msg.content = "开始咨询";
            msg.timestamp = date;
            sessions.saveMessageByTopic(msg, sessionId, function (err, msgId) {
                if (err) {
@ -135,7 +135,7 @@ class Topics extends RedisModel {
            msg.content = messages.description;
            msg.timestamp = new Date();
            sessions.saveMessageByTopic(msg, sessionId, function (err, msgId) {
                log.info("begin send" + messages.description);
                log.info("begin send " + messages.description);
            });
            if (messages.img) {
@ -152,6 +152,7 @@ class Topics extends RedisModel {
                    })
                }
            }
            ModelUtil.emitOK(self.eventEmitter, {"id": startMsgId});
        }
    }
@ -193,6 +194,7 @@ class Topics extends RedisModel {
    /**
     * 结束议题
     * @param topicId
     * @param endUser
     * @param endUserName
     */
    endTopic(topicId, endUser, endUserName) {
@ -236,6 +238,7 @@ class Topics extends RedisModel {
            dataArray.push(j);
            dataArray.push(valueJson[j]);
        }
        redis.hmsetAsync(topickey, dataArray).then(function (res) {
        });

+ 0 - 775
src/server/models/user/doctor.js

@ -1,775 +0,0 @@
/**
 * 医生模型。
 */
"use strict";
let log = require("../../util/log.js");
//let getui = require('getui');
let RedisModel = require('./../redis.model');
let Schedule = require("./../schedule/schedule.js");
let DoctorRepo = require('../../repository/mysql/doctor.repo.js');
let GroupMsgRepo = require('../../repository/mysql/group.msg.repo');
let PrivateMsgRepo = require('../../repository/mysql/private.msg.repo');
let NotifyMsgRepo = require("../../repository/mysql/notify.msg.repo");
let SystemMsgRepo = require("../../repository/mysql/system.msg.repo.js");
let StatsRepo = require("../../repository/mysql/stats.msg.repo");
let ObjectUtil = require("../../util/object.util.js");
let ModelUtil = require('../../util/model.util');
const CONTENT_TYPES = require('../../include/commons').CONTENT_TYPE;
const PLATFORMS = require('../../include/commons').PLATFORM;
const MAX_INT = require('../../include/commons').MAX_INT;
class Doctor extends RedisModel {
    constructor(doctorId) {
        super();
        this._id = doctorId;
    }
    get id(){
        return this._id;
    }
    /**
     * 向医生发送消息。
     *
     * @param message
     */
    sendMessage(message) {
        let self = this;
        let tempContent = message.contentType === CONTENT_TYPES.Article ? JSON.stringify(message.content) : message.content;
        PrivateMsgRepo.save(message.to, message.from, message.contentType, tempContent, function (err, result) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Save private message failed', err);
                return;
            }
            // 返回新插入的消息数据,并推送
            PrivateMsgRepo.findOneMessage(result.insertId, function (err, msg) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, 'Save private message success, but return last message failed', err);
                    return;
                }
                // 先结束网络连接,再推送给客户端
                ModelUtil.emitOK(self.eventEmitter, Doctor.fillMessages(msg));
                Doctor.pushMessage(message, 'p2p_msg');
            });
            // 更新自身的聊天统计信息
            StatsRepo.updatePrivateChatSummary(message.from, message.to, message.from, message.contentType, message.content, function (err, result) {
                if (err) log.error(err);
            });
            // 更新对端的聊天统计信息
            StatsRepo.updatePrivateChatSummary(message.to, message.from, message.from, message.contentType, message.content, function (err, result) {
                if (err) log.error(err);
            });
        });
    }
    /**
     * 向医生发送系统消息。
     *
     * @param message
     */
    sendSystemMessage(message) {
        let self = this;
        SystemMsgRepo.save(message.to,
            message.contentType,
            message.title,
            message.summary,
            message.content,
            function (err, result) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, "Save system message failed", err);
                    return;
                }
                // 先结束网络连接,再推送给客户端
                ModelUtil.emitOK(self.eventEmitter, {});
                Doctor.pushMessage(message, 'system_msg');
            });
    }
    /**
     * 推送消息。
     *
     * @param message
     * @param channel
     */
    static pushMessage(message, channel){
        DoctorRepo.getUserStatus(message.to, function (err, result) {
            if (err) {
                log.error('Lookup notify message receiver failed: ' + message.to);
                return;
            }
            if (result.length == 0) {
                log.warn('Notify message receiver is not found: ', message.to);
                return;
            }
            let userStatus = result[0];
            let isOnline = result.length > 0 && userStatus.is_online === 1;
            let delay = null;
            // 构建通知消息
            let notifyMessage = {type: channel, data: message.content};
            if (message.from) notifyMessage.from_uid = message.from;
            if (message.gid) notifyMessage.gid = message.gid;
            if (message.delay && message.delay !== "null") delay = new Date(Date.parse(message.delay));
            let title = '新消息';
            let content = message.content;
            if (message.contentType === CONTENT_TYPES.Image) {
                content = '[图片]';
            } else if (message.contentType === CONTENT_TYPES.Audio) {
                content = '[语音]';
            } else if (message.contentType > 3) {
                content = '您有一条新消息';
            }
            // 保存通知消息到数据库中,并根据用户在线状态推送此消息
            NotifyMsgRepo.save(message.to, message.contentType, title, content, JSON.stringify(notifyMessage), isOnline, delay, function (err, result) {
                if (err) {
                    log.error('Save notify message failed, ', err);
                    return;
                }
                if (delay) {
                    Schedule.dateSchedule(delay, function (message, client_id, status, token, title, content, notifyMessage, platform) {
                        Doctor.pushToClient(message.to, client_id, status, token, message.contentType,
                            title, content, notifyMessage, platform, function (err, result) {
                                if (err != null) {
                                    log.error(err);
                                } else {
                                    log.info(result);
                                }
                            });
                    }.bind(null, message, userStatus.client_id, userStatus.status, userStatus.token, title, content, notifyMessage, userStatus.platform));
                } else if (isOnline) {
                    Doctor.pushToClient(message.to, userStatus.client_id, userStatus.status, userStatus.token, message.contentType,
                        title, content, notifyMessage, userStatus.platform, function (err, result) {
                            if (err != null) {
                                log.error(err);
                            } else {
                                log.info(result);
                            }
                        });
                }
            });
        });
    }
    /**
     * 推送消息给医生客户端。
     *
     * @param userId            用户ID
     * @param clientId          客户端设备ID
     * @param appStatus         客户端App状态
     * @param token
     * @param contentType
     * @param title
     * @param content
     * @param notifyMessage
     * @param platform
     * @param handler
     */
    static pushToClient(userId, clientId, appStatus, token, contentType, title, content, notifyMessage, platform, handler) {
        if (platform === PLATFORMS.iOS) {
            getui.pushAPN(userId, token, contentType, title, content, notifyMessage, handler);
        } else if (platform === PLATFORMS.Android) {
            getui.pushAndroid(clientId, contentType, title, content, notifyMessage, appStatus, handler);
        }
    }
    /**
     * 获取最近聊天的用户,组。
     */
    getRecentChatList(userId, days) {
        let self = this;
        StatsRepo.getRecentChats(userId, days, function (err, rows) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Get recent chat objects failed', err);
                return;
            }
            let data = {patients: [], doctors: [], groups: []};
            if (rows.length > 0) {
                for (let i = 0; i < rows.length; ++i) {
                    let row = rows[i];
                    if (row.type.indexOf('patient') > -1) {
                        data.patients.push({
                            code: row.code,
                            name: row.name,
                            birthday: row.birthday === null ? "" : row.birthday,
                            sex: row.sex,
                            avatar: row.photo === null ? "" : row.photo
                        });
                    } else if (row.type.indexOf('doctor') > -1) {
                        data.doctors.push({
                            code: row.code,
                            name: row.name,
                            birthday: row.birthday === null ? "" : row.birthday,
                            sex: row.sex,
                            avatar: row.photo === null ? "" : row.photo
                        });
                    } else if (row.type.indexOf('group') > -1) {
                        data.groups.push({
                            code: row.code,
                            name: row.name
                        });
                    }
                }
            }
            ModelUtil.emitOK(self.eventEmitter, data);
        });
    }
    /**
     * 获取参与的聊天列表,包括:点对点,参与的讨论组,系统消息等。
     *
     * @param userId
     */
    getChatList(userId) {
        let self = this;
        // 与患者的私信
        PrivateMsgRepo.findAllP2PWithPatient(userId, function (err, patients) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Get chat list with patient failed', err);
                return;
            }
            let chats = {patients: [], doctors: [], groups: []};
            for (let i = 0; i < patients.length; i++) {
                let patient = patients[i];
                chats.patients.push({
                    code: patient.code,
                    name: patient.name,
                    birthday: patient.birthday,
                    sex: patient.sex,
                    avatar: patient.photo == null ? "" : patient.photo,
                    newMessageCount: patient.new_msg_count,
                    lastContentType: patient.last_content_type,
                    lastContent: patient.last_content,
                    timestamp: ObjectUtil.timestampToLong(patient.timestamp)
                });
            }
            // 含有患者的群
            GroupMsgRepo.findAllGroupsWithPatient(userId, function (err, groups) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, 'Get group list with patient failed', err);
                    return;
                }
                for (let i = 0; i < groups.length; i++) {
                    let group = groups[i];
                    // 过滤掉医生间的求助团队
                    if (group.group_type === 2) continue;
                    chats.groups.push({
                        code: group.code,
                        name: group.name,
                        groupType: group.msg_type,
                        newMessageCount: group.new_msg_count,
                        lastContentType: group.last_content_type,
                        lastContent: group.last_content,
                        timestamp: ObjectUtil.timestampToLong(group.timestamp)
                    });
                }
                // 医生间的私聊
                PrivateMsgRepo.findAllP2PWithDoctor(userId, function (err, doctors) {
                    if (err) {
                        ModelUtil.emitError(self.eventEmitter, 'Get chat list with doctor failed', err);
                        return;
                    }
                    for (let i = 0; i < doctors.length; i++) {
                        let doctor = doctors[i];
                        chats.doctors.push({
                            code: doctor.code,
                            name: doctor.name,
                            sex: doctor.sex,
                            avatar: doctor.photo === null ? "" : doctor.photo,
                            newMessageCount: doctor.new_msg_count,
                            lastContentType: doctor.last_content_type,
                            lastContent: doctor.last_content,
                            timestamp: ObjectUtil.timestampToLong(doctor.timestamp)
                        });
                    }
                    // 获取医生间的组
                    GroupMsgRepo.findAllGroupsWithDoctor(userId, function (err, groups) {
                        if (err) {
                            ModelUtil.emitError(self.eventEmitter, 'Get group list with doctor failed', err);
                            return;
                        }
                        for (let i = 0; i < groups.length; i++) {
                            let group = groups[i];
                            chats.groups.push({
                                code: group.code,
                                name: group.name,
                                groupType: group.group_type, // 行政团队 or 求助
                                newMessageCount: group.new_msg_count,
                                lastContentType: group.last_content_type,
                                lastContent: group.last_content,
                                timestamp: ObjectUtil.timestampToLong(group.timestamp)
                            });
                        }
                        ModelUtil.emitOK(self.eventEmitter, chats);
                    });
                });
            })
        });
    }
    /**
     * 获取与患者的聊天列表。
     */
    getChatsListWithPatient(userId) {
        let self = this;
        PrivateMsgRepo.findAllP2PWithPatient(userId, function (err, patients) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Get chat list with patient failed', err);
                return;
            }
            let chats = {patients: [], groups: []};
            for (let i = 0; i < patients.length; i++) {
                let patient = patients[i];
                chats.patients.push({
                    code: patient.code,
                    name: patient.name,
                    birthday: patient.birthday,
                    sex: patient.sex,
                    avatar: patient.photo == null ? "" : patient.photo,
                    newMessageCount: patient.new_msg_count,
                    lastContentType: patient.last_content_type,
                    lastContent: patient.last_content,
                    timestamp: ObjectUtil.timestampToLong(patient.timestamp)
                });
            }
            GroupMsgRepo.findAllGroupsWithPatient(userId, function (err, groups) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, 'Get group list with patient failed', err);
                    return;
                }
                for (let i = 0; i < groups.length; i++) {
                    let group = groups[i];
                    // 过滤掉医生间的求助团队
                    if (group.group_type === 2) continue;
                    chats.groups.push({
                        code: group.code,
                        name: group.name,
                        groupType: group.msg_type,
                        newMessageCount: group.new_msg_count,
                        lastContentType: group.last_content_type,
                        lastContent: group.last_content,
                        timestamp: ObjectUtil.timestampToLong(group.timestamp)
                    });
                }
                ModelUtil.emitOK(self.eventEmitter, chats);
            })
        });
    }
    /**
     * 获取与医生的聊天列表,包括:点对点,参与的讨论组。
     *
     * @param userId
     */
    getChatListWithDoctor(userId) {
        let self = this;
        // 先获取医生间的私聊
        PrivateMsgRepo.findAllP2PWithDoctor(userId, function (err, doctors) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Get chat list with doctor failed', err);
                return;
            }
            let chats = {doctors: [], groups: []};
            for (let i = 0; i < doctors.length; i++) {
                let doctor = doctors[i];
                chats.doctors.push({
                    code: doctor.code,
                    name: doctor.name,
                    sex: doctor.sex,
                    avatar: doctor.photo === null ? "" : doctor.photo,
                    newMessageCount: doctor.new_msg_count,
                    lastContentType: doctor.last_content_type,
                    lastContent: doctor.last_content,
                    timestamp: ObjectUtil.timestampToLong(doctor.timestamp)
                });
            }
            // 再获取医生间的组
            GroupMsgRepo.findAllGroupsWithDoctor(userId, function (err, groups) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, 'Get group list with doctor failed', err);
                    return;
                }
                for (let i = 0; i < groups.length; i++) {
                    let group = groups[i];
                    chats.groups.push({
                        code: group.code,
                        name: group.name,
                        groupType: group.group_type, // 行政团队 or 求助
                        newMessageCount: group.new_msg_count,
                        lastContentType: group.last_content_type,
                        lastContent: group.last_content,
                        timestamp: ObjectUtil.timestampToLong(group.timestamp)
                    });
                }
                ModelUtil.emitOK(self.eventEmitter, chats);
            });
        });
    }
    /**
     * 获取与医生,患者的聊天列表,包括:点对点,参与的讨论组。消息数量
     *
     * @param userId
     */
    getChatListMsgAmount(userId) {
        let self = this;
        let chats = {doctor: {}, patient: {}};
        // 先获取医生间的私聊
        PrivateMsgRepo.findAllP2PWithDoctor(userId, function (err, doctors) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Get chat list with doctor failed', err);
                return;
            }
            var amount = 0;
            for (let i = 0; i < doctors.length; i++) {
                let doctor = doctors[i];
                //过滤结束的咨询
                //if(doctor.last_content_type==7)continue;
                amount = doctor.new_msg_count+amount;
            }
            // 再获取医生间的组
            GroupMsgRepo.findAllGroupsWithDoctor(userId, function (err, groups) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, 'Get group list with doctor failed', err);
                    return;
                }
                for (let i = 0; i < groups.length; i++) {
                    let group = groups[i];
                    //过滤结束的咨询
                    //if(group.last_content_type==7)continue;
                    amount =   group.new_msg_count+amount;
                }
                chats.doctor = amount;
                var patientAmount =0;
                //获取患者记录数量
                PrivateMsgRepo.findAllP2PWithPatient(userId, function (err, patients) {
                    if (err) {
                        ModelUtil.emitError(self.eventEmitter, 'Get chat list with patient failed', err);
                        return;
                    }
                    for (let i = 0; i < patients.length; i++) {
                             let patient = patients[i];
                            //过滤结束的咨询
                            // if(patient.last_content_type==7)continue;
                             patientAmount =patientAmount+ patient.new_msg_count;
                    }
                    //获取患者记录数量
                    GroupMsgRepo.findAllGroupsWithPatient(userId, function (err, groups) {
                        if (err) {
                            ModelUtil.emitError(self.eventEmitter, 'Get group list with patient failed', err);
                            return;
                        }
                        for (let i = 0; i < groups.length; i++) {
                            let group = groups[i];
                            // 过滤掉医生间的求助团队
                            if (group.group_type === 2) continue;
                            //过滤结束的咨询
                            //if(group.last_content_type==7)continue;
                            patientAmount = patientAmount+ group.new_msg_count;
                        }
                        chats.patient = patientAmount;
                        ModelUtil.emitOK(self.eventEmitter, chats);
                    });
                });
            });
        });
    }
    /**
     * 获取与指定用户的聊天记录。
     *
     * @param userId
     * @param peerId
     * @param contentType
     * @param msgStartId
     * @param msgEndId
     * @param count
     * @param closedInterval
     */
    getPrivateMessages(userId, peerId, contentType, msgStartId, msgEndId, count, closedInterval) {
        let self = this;
        PrivateMsgRepo.findAllMessages(userId, peerId, contentType === undefined ? "0,1,2,3,4,5,6" : contentType, msgStartId, msgEndId, count, closedInterval, function (err, rows) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Get private message failed', err);
                return;
            }
            let messages = Doctor.fillMessages(rows);
            ModelUtil.emitOK(self.eventEmitter, messages);
            // 清空统计信息
            StatsRepo.clearPrivateChatSummary(userId, peerId, function (err, result) {
                if (err) log.error(err);
            });
        });
    }
    /**
     * 获取与某人聊天的未读消息数。
     *
     * @param userId
     * @param peerId
     */
    getUnreadMessageCount(userId, peerId) {
        let self = this;
        StatsRepo.getPrivateChatAllUnReadCount(userId, function (err, result) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, "Get unread private message count failed", err);
                return;
            }
            let data = {userId: userId, messageType: 1, newMessageCount: 0};
            for (let i = 0; i < result.length; i++) {
                data.newMessageCount += result[i].new_msg_count;
            }
            ModelUtil.emitOK(self.eventEmitter, data);
        });
    }
    /**
     * 获取所有未读的消息数,包括群。
     *
     * @param userId
     */
    getAllUnreadMessageCount(userId) {
        let self = this;
        StatsRepo.getChatAllUnReadCount(userId, function (err, result) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, "Get all unread message count failed", err);
                return;
            }
            let data = {userId: userId, messageType: 0, newMessageCount: 0};
            for (let index = 0; index < result.length; index++) {
                data.newMessageCount += result[index].new_msg_count;
            }
            ModelUtil.emitOK(self.eventEmitter, data);
        });
    }
    /**
     * 获取与指定用户的未读聊天记录。
     *
     * @param userId
     * @param peerId
     */
    getUnreadPrivateMessages(userId, peerId) {
        let self = this;
        StatsRepo.getPrivateChatSummary(userId, peerId, function (err, summary) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Get unread private messages failed', err);
                return;
            }
            // 没有未读消息,直接返回
            if (summary.length == 0 || summary[0].new_msg_count === 0) {
                ModelUtil.emitOK(self.eventEmitter, {startId: 0, count: 0, records: []});
                return;
            }
            PrivateMsgRepo.findUnread(peerId, userId, MAX_INT, summary[0].new_msg_count, function (err, rows) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, "Get unread private messages failed", err);
                    return;
                }
                let messages = Doctor.fillMessages(rows);
                ModelUtil.emitOK(self.eventEmitter, messages);
            });
        });
    }
    /**
     * 获取聊天统计摘要。
     *
     * @param userId
     * @param peerId
     */
    getChatSummary(userId, peerId) {
        let self = this;
        StatsRepo.getPrivateChatSummary(userId, peerId, function (err, result) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, "Get private messages statistic failed", err);
                return;
            }
            let data = {
                userId: userId,
                peerId: peerId,
                lastCContentType: 1,
                lastContent: "",
                newMessageCount: 0,
                timestamp: 0
            };
            if (result.length > 0) {
                let row = result[0];
                data.userId = row.uid;
                data.peerId = row.from_uid;
                data.lastContentType = row.last_content_type;
                data.lastContent = row.last_content;
                data.newMessageCount = row.new_msg_count;
                data.timestamp = ObjectUtil.timestampToLong(row.timestamp)
            }
            ModelUtil.emitOK(self.eventEmitter, data);
        });
    }
    getMessage(messageId, messageType) {
        let self = this;
        if (messageType == 1) {
            // 私信
            PrivateMsgRepo.findOneMessage(messageId, function (err, result) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, "Get message failed", err);
                    return;
                }
                if (result.length == 0) {
                    ModelUtil.emitDataNotFound(self.eventEmitter, "Message not found.");
                    return;
                }
                ModelUtil.emitOK(self.eventEmitter, {
                    id: result[0].msg_id,
                    from: result[0].from_uid,
                    to: result[0].to_uid,
                    contentType: result[0].type,
                    content: result[0].content,
                    timestamp: ObjectUtil.timestampToLong(result[0].timestamp)
                });
            })
        } else {
            // 群信
            GroupMsgRepo.findOneMessage(messageId, function (err, result) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, "Get message failed", err);
                    return;
                }
                if (result.length == 0) {
                    ModelUtil.emitDataNotFound(self.eventEmitter, "Message not found.");
                    return;
                }
                ModelUtil.emitOK(self.eventEmitter, {
                    id: result[0].msg_id,
                    from: result[0].from_uid,
                    at: result[0].at_uid,
                    groupId: result[0].to_gid,
                    contentType: result[0].type,
                    content: result[0].content,
                    timestamp: ObjectUtil.timestampToLong(result[0].timestamp)
                });
            });
        }
    }
    /**
     * 判断与患者的最新咨询会话是否已经结束。
     */
    isConsultFinished(doctorId, patientId) {
        let self = this;
        PrivateMsgRepo.isCurrentSessionFinished(doctorId, patientId, function (err, result) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, "Get session finish status failed: ", err);
                return;
            }
            let data = {finished: true, consultId: ''};
            if (result.length > 0) {
                let finishRow = result[0];
                data.finished = finishRow.finished === 1;
                if (!data.finished) {
                    data.consultId = finishRow.consult_id;
                }
            }
            ModelUtil.emitOK(self.eventEmitter, data);
        })
    }
    /**
     * 将消息的返回结果合并成JSON。
     *
     * @param rows
     *
     * @returns {startId: 0, count: 0, records: []}
     */
    static fillMessages(rows) {
        let messages = {startId: rows.length > 0 ? rows[0].msg_id : '', count: rows.length, records: []};
        for (let i = 0; i < rows.length; i++) {
            let row = rows[i];
            let record = {
                id: row.msg_id,
                from: row.from_uid,
                contentType: row.type,
                content: row.content,
                timestamp: ObjectUtil.timestampToLong(row.timestamp)
            };
            if (row.to_uid !== undefined) record.to = row.to_uid;
            if (row.at_uid !== undefined) record.at = row.at_uid;
            messages.records.push(record);
        }
        return messages;
    }
}
// Expose class
module.exports = Doctor;

+ 0 - 225
src/server/models/user/patient.js

@ -1,225 +0,0 @@
/**
 * 患者模型。
 */
"use strict";
let configFile = require('../../include/commons').CONFIG_FILE;
let config = require('../../resources/config/' + configFile);
let log = require("../../util/log.js");
let RedisModel = require('../redis.model');
let ObjectUtil = require("../../util/object.util.js");
let ModelUtil = require('../../util/model.util');
let WechatClient = require('../../models/wechat.client/wechat.client');
let Doctor = require('../../models/user/doctor');
let DoctorRepo = require('../../repository/mysql/doctor.repo');
let GroupRepo = require('../../repository/mysql/group.repo');
let PatientRepo = require('../../repository/mysql/patient.repo');
let StatsRepo = require("../../repository/mysql/stats.msg.repo");
let PmRepo = require('../../repository/mysql/private.msg.repo');
let clientCache = require('../socket.io/client.cache').clientCache();
const CONTENT_TYPES = require('../../include/commons').CONTENT_TYPE;
class Patient extends RedisModel {
    constructor(patientId) {
        super();
        this._id = patientId;
    }
    get id(){
        return this._id;
    }
    /**
     * 向患者发送消息。
     *
     * 注意:患者消息的保存发送与医生的实现不同。
     *
     * @param message 消息
     */
    sendMessage(message) {
        // 保存消息
        let self = this;
        let tempContent = message.contentType === CONTENT_TYPES.Article ? JSON.stringify(message.content) : message.content;
        PmRepo.save(message.to, message.from, message.contentType, tempContent, function (err, result) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, 'Save private message failed', err);
                return;
            }
            // 结束网络连接,后续操作继续执行
            PmRepo.findOnePatientMessage(result.insertId, function (err, msg) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, 'Save private message success, but return last message failed', err);
                    return;
                }
                ModelUtil.emitOK(self.eventEmitter, Doctor.fillMessages(msg));
                // 通过Web Socket推送给患者
                let patientClient = clientCache.findById(message.to);
                if (!patientClient) {
                    log.warn("User is not online, user id: ", message.to);
                    //发送微信模板消息
                    self.sendConsultWechatReplyTempMsg(message);
                    return;
                }
                let row = msg[0];
                row.timestamp = ObjectUtil.timestampToLong(row.timestamp);
                patientClient.socketServer.sockets.emit('message', row);
            });
            // 更新自身的聊天统计信息
            StatsRepo.updatePrivateChatSummary(message.from, message.to, message.from, message.contentType, message.content, function (err, result) {
                if (err) log.error(err);
            });
            // 更新对端的聊天统计信息
            StatsRepo.updatePrivateChatSummary(message.to, message.from, message.from, message.contentType, message.content, function (err, result) {
                if (err) log.error(err);
            });
        });
    };
    /**
     * 推送群组消息给居民
     *
     * @param message
     */
    pushGroupMessage(message) {
        let self = this;
        // 通过Web Socket推送给患者
        let patientClient = clientCache.findById(message.to);
        if (!patientClient) {
            log.warn("User is not online, user id: ", message.to);
            //发送微信模板消息
            self.sendConsultWechatReplyTempMsg(message);
            return;
        }
        GroupRepo.getOnGroupMsg(message.msgId, function (err, result) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, "get group msg info failed", err);
            }
            var msg = result ? result[0] : "";
            if (msg) {
                patientClient.socketServer.sockets.emit('message', msg);
            }
        })
    };
    /**
     * 发送微信模板消息给居民
     *
     * @param message
     */
    sendConsultWechatReplyTempMsg(message) {
        let self = this;
        // 发送微信消息
        function sendWxMessage(openid, name, consult) {
            var replyContent = message.content;
            switch (Number.parseInt(message.contentType)) {
                case CONTENT_TYPES.Image:
                    replyContent = "[图片]";
                    break;
                case CONTENT_TYPES.Audio:
                    replyContent = "[语音]";
                    break;
                case 0:
                case CONTENT_TYPES.Article:
                case CONTENT_TYPES.GoTo:
                case CONTENT_TYPES.TopicBegin:
                case CONTENT_TYPES.TopicEnd:
                    return;
                default:
                    break;
            }
            // 模板消息数据
            var msg = {
                touser: openid,
                template_id: config.wechatConfig.template.consultTemplate,
                url: config.wechatConfig.baseUrl + "/wx/html/yszx/html/consulting-doctor.html?openid=" + openid +
                "&consult=" + consult.consult + "&toUser=" + message.to,
                data: {
                    first: {value: "您的健康咨询有新的回复", color: "#000000"}
                    , remark: {value: "", color: "#000000"}
                    , keyword1: {value: consult.symptoms, color: "#000000"}
                    , keyword2: {value: replyContent, color: "#000000"}
                    , keyword3: {value: name, color: "#000000"}
                }
            };
            // 发送模板消息
            WechatClient.sendWxTemplateMessage(msg);
        }
        // 查询居民openid
        PatientRepo.findPatientOpenId(message.to, function (err, result) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, "get patient openid failed", err);
                return;
            }
            var openid = result && result.length > 0 ? result[0].openid : "";
            if (openid) {
                // 查询医生信息
                DoctorRepo.findOne(message.from, function (err, result) {
                    if (err) {
                        ModelUtil.emitError(self.eventEmitter, "get doctor info failed", err);
                        return;
                    }
                    if (result && result.length > 0) {
                        var name = result[0].name;
                        if (message.group) {
                            GroupRepo.getGroupConsultInfo(message.group, function (err, result) {
                                if (err) {
                                    ModelUtil.emitError(self.eventEmitter, "get patient and doctor consult info failed", err);
                                    return;
                                }
                                var consult = result && result.length > 0 ? result[0] : "";
                                if (consult) {
                                    sendWxMessage(openid, name, consult);
                                }
                            });
                        } else {
                            // 查询医生与居民对应的咨询信息
                            PatientRepo.getPatientDoctorConsult(message.to, message.from, function (err, result) {
                                if (err) {
                                    ModelUtil.emitError(self.eventEmitter, "get patient and doctor consult info failed", err);
                                    return;
                                }
                                var consult = result && result.length > 0 ? result[0] : "";
                                if (consult) {
                                    sendWxMessage(openid, name, consult);
                                }
                            });
                        }
                    } else {
                        ModelUtil.emitError(self.eventEmitter, "can not find doctor info", err);
                    }
                });
            } else {
                ModelUtil.logError("patient does not bind wechat", err);
            }
        });
    };
}
module.exports = Patient;

+ 6 - 87
src/server/models/user/users.js

@ -6,21 +6,17 @@
 */
"use strict";
let RedisClient = require('../../repository/redis/redis.client');
let RedisModel = require('../redis.model');
let Doctor = require('./doctor');
let Patient = require('./patient');
let ImDb = require('../../repository/mysql/db/im.db');
let ParticipantRepo = require('../../repository/mysql/participant.repo');
let DoctorRepo = require('../../repository/mysql/doctor.repo');
let PatientRepo = require('../../repository/mysql/patient.repo');
let AppStatusRepo = require('../../repository/mysql/app.status.repo');
let SessionRepo = require('../../repository/mysql/session.repo');
let TopicRepo = require('../../repository/mysql/topics.repo');
let MessageRepo = require('../../repository/mysql/message.repo');
let TopicRepo = require('../../repository/mysql/topics.repo');
let ModelUtil = require('../../util/model.util');
let ObjectUtil = require("../../util/object.util.js");
let RedisClient = require('../../repository/redis/redis.client');
let redisConn = RedisClient.redisClient().connection;
let async = require('async');
@ -69,83 +65,6 @@ class Users extends RedisModel {
        ]);
    }
    /**
     * 取得用户微信端状态。
     *
     * @param userId
     * @param outCallback
     */
    getWechatStatus(userId) {
        let self = this;
        redisConn.hgetallAsync(RedisModel.makeRedisKey(REDIS_KEYS.UserWechatStatus, userId))
            .then(function (res) {
                if (res) {
                    ModelUtil.emitOK(self, res);
                } else {
                    ModelUtil.emitDataNotFound(self, {"message": "User is offline, unable to get wechat status."});
                }
            });
    }
    /**
     * 获取客户端App状态。
     *
     * @param userId
     * @param outCallback
     */
    getAppStatus(userId, outCallback) {
        let self = this;
        async.waterfall([
            // get from redis
            function (callback) {
                let userStatusKey = RedisModel.makeRedisKey(REDIS_KEYS.UserStatus, userId);
                redisConn.hgetallAsync(userStatusKey).then(function (res) {
                    if (res === null) {
                        callback(null);  // get from mysql
                    } else {
                        outCallback(null, res);
                    }
                });
            },
            // get from MySQL
            function () {
                AppStatusRepo.findOne(userId, function (err, res) {
                    let userStatus = null;
                    if (res.length > 0) {
                        userStatus = {};
                        userStatus.platform = res[0].platform;
                        userStatus.token = res[0].token;
                        userStatus.client_id = res[0].client_id;
                        userStatus.app_in_bg = res[0].app_in_bg;
                        userStatus.last_login_time = res[0].last_login_time;
                    }
                    outCallback(null, userStatus);
                });
            }
        ]);
    }
    /**
     * 更新客户端App状态。
     *
     * @param userId
     * @param appInBg
     */
    updateAppStatus(userId, appInBg) {
        let self = this;
        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) {
                    ModelUtil.emitOK(self.eventEmitter, {});
                });
            } else {
                ModelUtil.emitDataNotFound(self.eventEmitter, {"message": "User is offline, unable to update app status."});
            }
        });
    }
    /**
     * 用户登录,仅缓存用户客户端状态信息,不缓存用户基本信息。
     *
@ -212,7 +131,7 @@ class Users extends RedisModel {
                        sessions.forEach(function (session) {
                            redisConn.zscore(REDIS_KEYS.Sessions, session.id, function (err, res) {
                                // 已经缓存过的会话不再缓存
                                if(res != null) return;
                                if (res != null) return;
                                (function (sessionId, userId) {
                                    let redisSession = [
@ -238,7 +157,7 @@ class Users extends RedisModel {
                                            // cache participants
                                            let sessionParticipantsKey = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipants, sessionId);
                                            let sessionParticipantsRoleKey = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipantsRole, sessionId);
                                            ParticipantRepo.findParticipants(sessionId, function (err, participants) {
                                            ParticipantRepo.findAll(sessionId, function (err, participants) {
                                                if (err) {
                                                    ModelUtil.emitError(self.eventEmitter, err.message);
                                                    return;
@ -246,8 +165,8 @@ class Users extends RedisModel {
                                                let multi = redisConn.multi();
                                                participants.forEach(function (participant) {
                                                    let participantId = participant.participant_id;
                                                    let participantRole = participant.participant_role;
                                                    let participantId = participant.id;
                                                    let participantRole = participant.role;
                                                    let score = ObjectUtil.timestampToLong(participant.last_fetch_time);
                                                    multi = multi.zadd(sessionParticipantsKey, score, participantId)

+ 0 - 148
src/server/models/wechat.client/wechat.client.js

@ -1,148 +0,0 @@
/**
 * 微信客户端。
 *
 * author: lyr
 * author: sand
 * since: 2016/11/25
 */
"use strict"
let https = require('https');
let configFile = require('../../include/commons').CONFIG_FILE;
let config = require('../../resources/config/' + configFile);
let log = require('../../util/log');
let WechatTokenRepo = require('../../repository/mysql/wechat.token.repo.js');
class WeChatClient {
    constructor() {
    }
    /**
     * 发送微信模板消息
     *
     * @param message {touser:"", template_id:"", url:"", data:{firts: {value:"", color:""}}}
     */
    static sendWxTemplateMessage(message, handler) {
        WehcatClient.getAccessToken(function (err, token) {
            if (err) {
                log.error("get access_token failed:" + err);
                return;
            }
            var opt = {
                host: 'api.weixin.qq.com',
                path: '/cgi-bin/message/template/send?access_token=' + token,
                method: 'POST'
            };
            var msg = JSON.stringify(message);
            log.info("sending wechat template message:" + msg);
            // 发送模板消息
            var req = https.request(opt, function (res) {
                res.setEncoding('utf8');
                var data = "";
                res.on('data', (d) => {
                    data += d;
                });
                res.on('end', () => {
                    var result = JSON.parse(data);
                    if (result && result.errcode === 0) {
                        log.info("send wechat template message success:" + msg);
                        if (handler) {
                            handler(null, result);
                        }
                    } else {
                        log.error("send wechat template message failed:" + msg);
                        if (handler) {
                            handler(result, null);
                        }
                    }
                });
                res.on('error', function (err) {
                    log.error('send wechat template message failed: ' + err.message);
                    if (handler) {
                        handler(err, null);
                    }
                });
            }).on('error', (e) => {
                log.error("send wechat template message failed:" + e.message);
                if (handler) {
                    handler(e, null);
                }
            });
            req.end(msg);
        });
    };
    /**
     * 获取微信access_token
     *
     * @param handler 回调函数
     */
    static getAccessToken(handler) {
        WechatTokenRepo.findOne(function (err, result) {
            if (err) {
                log.error("get wechat accessToken failed", err);
                return;
            }
            var data = result && result.length > 0 ? result[0] : null;
            var accessToken = "";
            if (data) {
                // 判断access_token是否有效
                if ((new Date().getTime() - data.add_timestamp) < (data.expires_in * 1000)) {
                    accessToken = data.access_token;
                }
            }
            // access_token为空时从微信新获取并执行回调,否则直接执行回调
            if (!accessToken) {
                var token_url = "https://api.weixin.qq.com/cgi-bin/token?";
                var params = "grant_type=client_credential&appid=" + config.wechatConfig.appId
                    + "&secret=" + config.wechatConfig.appSecret;
                // 从微信获取access_token
                https.get(token_url + params, function (res) {
                    var data = '';
                    res.on('data', (d) => {
                        data += d;
                    });
                    res.on('end', () => {
                        data = data ? JSON.parse(data) : {};
                        if (data.access_token) {
                            accessToken = data.access_token;
                            var expiresIn = data.expires_in;
                            WechatTokenRepo.save(accessToken, expiresIn, new Date(), function (err, result) {
                                if (err) {
                                    log.error("insert wechat access_token failed:" + err.message);
                                }
                            });
                            if (handler) handler(null, accessToken);
                        } else {
                            log.error("get wechat access_token failed:" + data);
                            if (handler) handler(data, null);
                        }
                    });
                }).on('error', (e) => {
                    log.error("get wechat access_token from wechat failed:" + e.message);
                    if (handler) handler(data, null);
                });
            } else {
                if (handler) handler(data, null);
            }
        });
    };
}
module.exports = WeChatClient;

+ 0 - 386
src/server/public/javascripts/im.client.js

@ -1,386 +0,0 @@
/**
 * IM客户端SDK。此SDK可以连接开发、测试或生产环境,根据需要配置环境参数以连接到不同的服务器。
 */
// Node.js模拟jQuery及ajax请求所需要的环境:document及XMLHttpRequest。
// 这些环境仅用于模拟,客户端在使用时候不需要真正引入
if (typeof process !== 'undefined') {
    var jsdom = require('jsdom').jsdom;
    var document = jsdom('<html></html>', {});
    var window = document.defaultView;
    var jQuery = require('jquery');
    var $ = jQuery(window);
    $.support.cors = true;
    XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
    $.ajaxSettings.xhr = function () {
        return new XMLHttpRequest();
    };
}
// 本地临时缓存Key
var LocalStorageKey = {
    userId: "im_userid"
};
// 服务器
var server = "http://127.0.0.1:3008/api/v2";
// 资源实体在URL中的占位符
var UserPath = ":user_id";
var SessionPath = ":session_id";
var TopicPath = ":topic_id";
var ParticipantPath = ":participant_id";
// REST API
var ENDPOINTS = {
    Application: {
        BadgeNo: '/application/badge_no'
    },
    Management: {
        Health: '/management/health',
        DbStatus: '/management/db'
    },
    Users: {
        Login: '/users/login',
        Logout: '/users/logout',
        User: '/users/:user_id',
        UserStatus: '/users/:user_id/status'
    },
    Sessions: {
        Sessions: '/sessions',
        Session: '/sessions/:session_id',
        SessionSticky: '/sessions/:session_id/sticky',
        RecentSessions: '/sessions/recent',
        Topics: '/sessions/:session_id/topics',
        Topic: '/sessions/:session_id/topics/:topic_id',
        TopicEnded: '/sessions/:session_id/topics/:topic_id/ended',
        Messages: '/sessions/:session_id/messages',
        MessagesByTopic: '/sessions/:session_id/topics/:topic_id/messages',
        Message: '/sessions/:session_id/messages/:message_id',
        SessionsUnreadMessageCount: '/sessions/unread_message_count',
        SessionUnreadMessageCount: '/sessions/:session_id/unread_message_count',
        SessionUnreadMessages: '/sessions/:session_id/messages/unread',
        Participants: '/sessions/:session_id/participants',
        ParticipantsAvatar: '/sessions/:session_id/participants/avatars',
        Participant: '/sessions/:session_id/participants/:participant_id',
        ParticipantAvatar: '/session/:session_id/participants/:participant_id/avatars'
    },
    Search: {}
};
var httpClient = {
    get: function (endpoint, data, successCallback, errorCallback) {
        $.ajax({
            type: "get",
            url: server + endpoint,
            data: data,
            async: true,
            dataType: "json",
            success: function (data) {
                successCallback(data);
            },
            error: function (xhr, status, error) {
                errorCallback(xhr, status, error);
            }
        });
    },
    post: function (endpoint, data, successCallback, errorCallback) {
        $.ajax({
            type: "post",
            url: server + endpoint,
            data: data,
            async: true,
            dataType: "json",
            success: function (data) {
                successCallback(data);
            },
            error: function (xhr, status, error) {
                errorCallback(xhr, status, error);
            }
        });
    },
    put: function (endpoint, data, successCallback, errorCallback) {
        $.ajax({
            type: "post",
            url: server + endpoint,
            data: data,
            async: true,
            dataType: "json",
            headers: {
                "X-HTTP-Method-Override": "PUT"
            },
            success: function (data) {
                successCallback(data);
            },
            error: function (xhr, status, error) {
                errorCallback(xhr, status, error);
            }
        });
    },
    delete: function (endpoint, data, successCallback, errorCallback) {
        $.ajax({
            type: "get",
            url: server + endpoint,
            data: data,
            async: true,
            dataType: "json",
            headers: {
                "X-HTTP-Method-Override": "DELETE"
            },
            success: function (data) {
                successCallback(data);
            },
            error: function (xhr, status, error) {
                errorCallback(xhr, status, error);
            }
        });
    },
    // 执行业务接口前,调用此函数判断当前用户是否在线。
    isOnline: function (callback, failure) {
        httpClient.get(ENDPOINTS.Users.UserStatus, {}, null, function (res) {
            if (res.status == 200) {
                callback();
            } else {
                failure();
            }
        });
    }
};
var imClient = {
    Application: {
        // 获取应用角标数
        getBadgeNo: function (userId, success, failure) {
            httpClient.get(ENDPOINTS.Application.BadgeNo,
                {user_id: userId},
                success,
                failure);
        }
    },
    Management: {
        getDbStatus: function (success, failure) {
            httpClient.get(ENDPOINTS.Management.DbStatus,
                {},
                success,
                failure);
        }
    },
    Users: {
        // 登录
        login: function (userId, token, client_id, platform, success, failure) {
            if (typeof plus !== 'undefined') plus.storage.setItem(LocalStorageKey.userId, userId);
            httpClient.post(ENDPOINTS.Users.Login,
                {user_id: userId, token: token, client_id: client_id, platform: platform},
                success,
                failure);
        },
        // 退出
        logout: function (userId, success, failure) {
            if (typeof plus !== 'undefined') plus.storage.removeItem(LocalStorageKey.userId);
            httpClient.delete(ENDPOINTS.Users.Logout,
                {user_id: userId},
                success,
                failure);
        },
        // 更新用户状态
        updateStatus: function (userId, appInBg, success, failure) {
            httpClient.put(ENDPOINTS.Users.UserStatus.replace(UserPath, userId),
                {app_in_bg: appInBg},
                success,
                failure);
        },
    },
    Sessions: {
        // 创建MUC会话
        createMucSession: function (userId, peerId, success, failure) {
            httpClient.post(ENDPOINTS.Sessions.Sessions,
                {session_type: 1, session_name: "咨询", participants: []},
                success,
                failure);
        },
        // 创建P2P会话
        createP2pSession: function (userId, peerId, success, failure) {
            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.Sessions,
                {user_id: userId, session_type: "1", page: page, size: size},
                success,
                failure);
        },
        // 获取与医生相关的会话,实际上这些是P2P,群聊和讨论组
        getSessionsWithDoctor: function (userId, page, size, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.Sessions,
                {user_id: userId, session_type: "2,3", page: page, size: size},
                success,
                failure);
        },
        // 获取与患者的最近会话
        getRecentSessionsWithPatient: function (userId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.RecentSessions,
                {session_type: 1, user_id: userId},
                success,
                failure);
        },
        // 获取与医生的最近会话,包括P2P、群聊与讨论组
        getRecentSessionsWithDoctor: function (userId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.RecentSessions,
                {session_type: "2, 3", userId: userId},
                success,
                failure);
        },
        // 置顶会话
        stickSession: function (userId, sessionId, success, failure) {
            httpClient.put(ENDPOINTS.Sessions.SessionSticky.replace(SessionPath, sessionId),
                {user_id: userId},
                success,
                failure);
        },
        // 取消会话置顶
        unstickSession: function (userId, sessionId, success, failure) {
            httpClient.put(ENDPOINTS.Sessions.SessionSticky.replace(SessionPath, sessionId),
                {user_id: userId},
                success,
                failure);
        },
        // 获取所有咨询
        getTopics: function (sessionId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.Topics.replace(SessionPath, sessionId),
                {},
                success,
                failure);
        },
        // 创建咨询
        createTopic: function (sessionId, userId, success, failure) {
            httpClient.post(ENDPOINTS.Sessions.Topics.replace(SessionPath, sessionId),
                {user_id: userId},
                success,
                failure);
        },
        // 结束咨询
        endTopic: function (sessionId, userId, success, failure) {
            httpClient.put(ENDPOINTS.Sessions.Topics.replace(SessionPath, sessionId),
                {user_id: userId},
                success,
                failure);
        },
        // 咨询是否已结束
        isTopicEnded: function (sessionId, topicId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.TopicEnded.replace(SessionPath, sessionId).replace(TopicPath, topicId),
                {},
                success,
                failure);
        },
        // 发送消息,不论是何种会话,均不需要指定会话对象是谁,只需要向会话本身发送消息即可
        sendMessage: function (sessionId, userId, userName, content, contentType, success, failure) {
            httpClient.post(ENDPOINTS.Sessions.Messages.replace(SessionPath, sessionId),
                {sender_id: userId, sender_name: userName, content_type: contentType, content: content},
                success,
                failure);
        },
        // 按会话获取消息
        getMessagesBySession: function (sessionId, startMessageId, count, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.Messages.replace(SessionPath, sessionId),
                {start_message_id: startMessageId, count: count},
                success,
                failure);
        },
        // 按议题获取消息
        getMessagesByTopic: function (sessionId, topicId, startMessageId, count, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.MessagesByTopic.replace(SessionPath, sessionId),
                {start_message_id: startMessageId, count: count},
                success,
                failure);
        },
        // 获取所有会话的未读消息数
        getAllSessionUnreadMessageCount: function (userId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.SessionsUnreadMessageCount,
                {user_id: userId},
                success,
                failure);
        },
        // 获取指定会话的未读消息数
        getSessionUnreadMessageCount: function (sessionId, userId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.SessionUnreadMessageCount.replace(SessionPath, sessionId),
                {user_id: userId},
                success,
                failure);
        },
        // 获取指定会话的未读消息
        getSessionUnreadMessages: function (sessionId, userId, count, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.SessionUnreadMessages.replace(SessionPath, sessionId),
                {user_id: userId},
                success,
                failure);
        },
        // 添加会话成员
        addParticipant: function (sessionId, participantId, success, failure) {
            httpClient.post(ENDPOINTS.Sessions.SessionParticipants.replace(SessionPath, sessionId),
                {participant_id: participantId},
                success,
                failure);
        },
        // 获取会话成员列表
        getParticipants: function (sessionId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.SessionParticipants.replace(SessionPath, sessionId),
                {},
                success,
                failure);
        },
        // 移除会话成员
        removeParticipant: function (sessionId, participantId, success, failure) {
            httpClient.delete(ENDPOINTS.Sessions.Participant.replace(SessionPath, sessionId).replace(ParticipantPath, participantId),
                {},
                success,
                failure);
        },
        // 获取会话成员头像
        getParticipantAvatar: function (sessionId, participantId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.ParticipantAvatar.replace(SessionPath, sessionId).replace(ParticipantPath, participantId),
                {},
                success,
                failure);
        }
    },
    Search: {}
};
module.exports = imClient;

+ 3 - 2
src/server/repository/mysql/doctor.repo.js

@ -1,6 +1,7 @@
/**
 * 医生库。医生真实数据存在于家庭医生平台数据库,即医生的ID,姓名,年龄等详细内容。而IM平台的user表只包含用户当前的在线状态,
 * 即用户的准实时状态,token等内容,不包含用户具体的详细信息。
 * i健康用户库,即医生数据,此命名为解藉用户与特定角色的关条。
 *
 * 目前,医生真实数据存在于家庭医生平台数据库,IM数据库仅是做视图。将来根据需要将通过同步机制将数据同步到IM数据库。
 */
"use strict";

File diff suppressed because it is too large
+ 0 - 117
src/server/repository/mysql/group.msg.repo.js


+ 0 - 104
src/server/repository/mysql/group.repo.js

@ -1,104 +0,0 @@
/**
 * 群组模型。此数据来源于家庭医生平台数据库的行政团队。
 *
 * 实际团队数据分为两部分:行政团队与临时讨论组,具体的与业务相关。医生在讨论组里,
 * 会出现两种:在行政团队内聊天,也可以在临时讨论组里聊天。
 */
"use strict";
let ImDb = require("../mysql/db/im.db.js");
let GROUP_TYPES = require('../../include/commons').GROUP_TYPE;
class GroupRepo {
    constructor() {
    }
    static getOnGroupMsg(msgid, handler) {
        ImDb.execQuery({
            "sql": " select g.*,d.`name`,d.photo from msg_group g,wlyy.wlyy_doctor d where g.msg_id=? and g.from_uid=d.code;",
            "args": [msgid],
            "handler": handler
        });
    }
    /**
     * 判断是否为团队成员。
     *
     * @param groupId
     * @param groupType
     * @param doctorId
     * @param handler
     */
    static isGroupMember(groupId, groupType, doctorId, handler) {
        if (groupType == GROUP_TYPES.AdminTeam) {
            ImDb.execQuery({
                "sql": "SELECT doctor_code user_id from wlyy_admin_team_member WHERE team_id=? and doctor_code=?",
                "args": [groupId, doctorId],
                "handler": handler
            });
        } else {
            ImDb.execQuery({
                "sql": "SELECT member_code user_id from wlyy_talk_group_member WHERE group_code=? and member_code=?",
                "args": [groupId, doctorId],
                "handler": handler
            });
        }
    };
    /**
     * 获取团队成员。
     *
     * @param groupId
     * @param groupType
     * @param handler
     */
    static getMembers(groupId, groupType, handler) {
        if (groupType == GROUP_TYPES.AdminTeam) {
            ImDb.execQuery({
                "sql": "SELECT doctor_code user_id from wlyy_admin_team_member WHERE team_id=? AND available = 1",
                "args": [groupId],
                "handler": handler
            });
        } else {
            ImDb.execQuery({
                "sql": "SELECT member_code user_id from wlyy_talk_group_member WHERE group_code=?",
                "args": [groupId],
                "handler": handler
            });
        }
    };
    static getMembersAvatar(groups, handler) {
        var sql = "SELECT * FROM(" +
            "SELECT m.group_code g_code, d.code code, d.name name, d.photo photo, 'doctor' type FROM " +
            "wlyy.wlyy_talk_group_member m, wlyy.wlyy_doctor d " +
            "WHERE m.member_code = d.code AND m.group_code IN (" + groups + ") " +
            " UNION " +
            "SELECT m.group_code g_code, p.code code, p.name name, p.photo photo, 'patient' type " +
            "FROM  wlyy.wlyy_talk_group_member m, wlyy.wlyy_patient p " +
            "WHERE m.member_code = p.code AND m.group_code IN (" + groups + ")" +
            " UNION " +
            "SELECT m.team_id g_code, d.code code, d.name name, d.photo photo, 'doctor' type " +
            "FROM wlyy.wlyy_admin_team_member m, wlyy.wlyy_doctor d " +
            "WHERE m.doctor_code = d.code AND m.team_id IN (" + groups + ") " +
            ") x ORDER BY x.g_code";
        ImDb.execQuery({
            "sql": sql,
            "args": [],
            "handler": handler
        });
    };
    static getGroupConsultInfo(code, handler) {
        var sql = "select t.* from wlyy_talk_group g join wlyy_consult_team t on g.consult_code = t.consult where g.code = ? and g.type = 1 and t.status = 0 and t.del = '1'";
        ImDb.execQuery({
            "sql": sql,
            "args": [code],
            "handler": handler
        });
    }
}
module.exports = GroupRepo;

+ 0 - 46
src/server/repository/mysql/notify.msg.repo.js

@ -1,46 +0,0 @@
/**
 * 保存服务端向客户端发送的“推送消息”。当用户A向用户B发送消息,服务器会保存此消息,并构建一条通知消息保存到数据库,
 * 并通过个推系统向B发送此消息。
 */
"use strict";
let log = require('../../util/log');
let ImDb = require("../mysql/db/im.db.js");
class NotifyMsgRepo {
    /**
     * 保存推送消息。
     *
     * @param to
     * @param contentType
     * @param title
     * @param content
     * @param message
     * @param has_pushed
     * @param delay
     * @param handler
     */
    static save(to, contentType, title, content, message, has_pushed, delay, handler) {
        ImDb.execQuery({
            "sql": "INSERT INTO push_notify (to_uid, type, title, content, data, delay, has_pushed) VALUES (?,?,?,?,?,?,?)",
            "args": [to, contentType, title, content, message, delay, has_pushed],
            "handler": handler
        });
    };
    /**
     * 查找未推送的消息。
     */
    static findUnpushedMessages(handler) {
        let sql = "select to_uid, title, type, content, data, delay " +
            "FROM push_notify " +
            "WHERE delay IS NOT NULL AND delay > now()";
        ImDb.execQuery({
            "sql": sql,
            "args": [],
            "handler": handler
        });
    };
}
module.exports = NotifyMsgRepo;

+ 29 - 5
src/server/repository/mysql/participant.repo.js

@ -18,12 +18,34 @@ class ParticipantRepo {
     * @param sessionId
     * @param handler
     */
    static findParticipants(sessionId, handler) {
        let sql = "select participant_id, participant_role, last_fetch_time from participants where session_id = ? order by participant_id";
    static findAll(sessionId, handler) {
        let sql = "select u.id, u.name, u.sex, u.birthdate, u.avatar, p.participant_role role, false is_patient from sessions s, participants p, doctors u " +
            "where s.id = ? and s.id = p.session_id and p.participant_id = u.id union " +
            "select u.id, u.name, u.sex, u.birthdate, u.avatar, p.participant_role role, true is_patient from sessions s, participants p, patients u " +
            "where s.id = ? and s.id = p.session_id and p.participant_id = u.id";
        ImDb.execQuery({
            "sql": sql,
            "args": [sessionId],
            "args": [sessionId, sessionId],
            "handler": handler
        });
    }
    /**
     * 获取会话的成员头像列表
     *
     * @param sessionId
     * @param handler
     */
    static findAllAvatars(sessionId, handler) {
        let sql = "select u.id, u.avatar from sessions s, participants p, doctors u " +
            "where s.id = ? and s.id = p.session_id and p.participant_id = u.id union " +
            "select u.id, u.avatar from sessions s, participants p, patients u " +
            "where s.id = ? and s.id = p.session_id and p.participant_id = u.id";
        ImDb.execQuery({
            "sql": sql,
            "args": [sessionId, sessionId],
            "handler": handler
        });
    }
@ -32,13 +54,15 @@ class ParticipantRepo {
     * 获取会话的成员列表
     *
     * @param sessionId
     * @param participantId
     * @param role
     * @param handler
     */
    static updateParticipant(sessionId, participant_id, role,handler) {
    static updateParticipant(sessionId, participantId, role, handler) {
        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, participantId],
            "handler": handler
        });
    }

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

@ -1,5 +1,7 @@
/**
 * 患者库。
 * 微信用户库,即患者数据库。
 *
 * 目前,患者真实数据存在于家庭医生平台数据库,IM数据库仅是做视图。将来根据需要将通过同步机制将数据同步到IM数据库。
 *
 * author: Sand
 * since: 2016/11/18
@ -20,7 +22,7 @@ class PatientRepo {
        });
    }
    static findPatientOpenId(code, handler) {
    static findWechatOpenId(code, handler) {
        var sql = "select openid from patients where id = ? ";
        ImDb.execQuery({

+ 0 - 292
src/server/repository/mysql/stats.msg.repo.js

@ -1,292 +0,0 @@
/**
 * 消息统计。方便前端获取聊天的状态。
 */
"use strict";
var http = require('http');
var async = require('async');
var configFile = require('../../include/commons').CONFIG_FILE;
var config = require('../../resources/config/' + configFile);
var log = require('../../util/log');
var ImDb = require("../mysql/db/im.db.js");
var WLYY_ENDPOINTS = require('../../include/wlyy.endpoints').WLYY_ENDPOINTS;
class StatsRepo {
    constructor() {
    }
    //--------------------About all chats--------------------
    /**
     * 所有聊天列表。
     *
     * @param userId
     * @param handler
     */
    static getChatList(userId, handler) {
        ImDb.execQuery({
            "sql": "SELECT uid,from_uid,from_gid,peer_uid,at_me,msg_type,last_content_type,last_content,new_msg_count,timestamp from msg_statistic WHERE uid = ?",
            "args": [userId],
            "handler": handler
        });
    };
    /**
     * 所有未读聊天记录数。
     *
     * @param userId
     * @param handler
     */
    static getChatAllUnReadCount(userId, handler) {
        ImDb.execQuery({
            "sql": "SELECT new_msg_count from msg_statistic WHERE uid=? AND new_msg_count>0",
            "args": [userId],
            "handler": handler
        });
    };
//--------------------About private chat summary--------------------
    static updatePrivateChatSummary(userId, peerId, from, type, content, handler) {
        var uuid = userId + '_' + peerId;
        if (userId == from) {
            //userId  = from ,peerId = to from = from
            // 更新自身的统计信息
            var sql = "INSERT INTO msg_statistic (uid,uuid,from_uid,peer_uid,msg_type,last_content_type,last_content,new_msg_count) " +
                "VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE peer_uid=?,last_content_type=?,last_content=?";
            ImDb.execQuery({
                "sql": sql,
                "args": [userId, uuid, from, peerId, 1, type, content, 0, peerId, type, content],
                "handler": handler
            });
        } else {
            var sql = "";
            if (type == 7) {//结束的咨询
                //userId  = to ,peerId = from, from = from
                sql = "INSERT INTO msg_statistic (uid,uuid,from_uid,peer_uid,msg_type,last_content_type,last_content) " +
                    "VALUES (?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE peer_uid=?,last_content_type=?,last_content=?";
                // 更新对端的统计信息
                ImDb.execQuery({
                    "sql": sql,
                    "args": [userId, uuid, from, peerId, 1, type, content, peerId, type, content],
                    "handler": handler
                });
            } else {
                //userId  = to ,peerId = from, from = from
                sql = "INSERT INTO msg_statistic (uid,uuid,from_uid,peer_uid,msg_type,last_content_type,last_content,new_msg_count) " +
                    "VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE peer_uid=?,last_content_type=?,last_content=?,new_msg_count=new_msg_count+1";
                // 更新对端的统计信息
                ImDb.execQuery({
                    "sql": sql,
                    "args": [userId, uuid, from, peerId, 1, type, content, 1, peerId, type, content],
                    "handler": handler
                });
            }
        }
    };
    static clearPrivateChatSummary(userId, peerId, handler) {
        var uuid = userId + '_' + peerId;
        ImDb.execQuery({
            "sql": "UPDATE msg_statistic SET new_msg_count='0' WHERE uuid=?",
            "args": [uuid],
            "handler": handler
        });
    };
    static getPrivateChatSummary(userId, peerId, handler) {
        var uuid = userId + '_' + peerId;
        ImDb.execQuery({
            "sql": "SELECT uid,from_uid,last_content_type,last_content,new_msg_count,timestamp from msg_statistic WHERE uuid = ?",
            "args": [uuid],
            "handler": handler
        });
    };
    static getPrivateChatAllUnReadCount(userId, handler) {
        ImDb.execQuery({
            "sql": "SELECT new_msg_count from msg_statistic WHERE uid = ? AND msg_type = 1 AND new_msg_count > 0",
            "args": [userId],
            "handler": handler
        });
    };
    /**
     * 最近聊天对象,如患者,医生与群等基本信息。
     *
     * @param userId
     * @param days
     * @param handler
     */
    static getRecentChats(userId, days, handler) {
        var timespan = 60 * 60 * 24 * days; // 多少天内的联系对象
        var sql = "SELECT * FROM(" +
            "SELECT DISTINCT p.code code, p.name name, p.birthday birthday, p.sex sex, p.photo photo, ms.timestamp timestamp, 'patient' type " +
            "FROM msg_statistic ms, wlyy.wlyy_patient p " +
            "WHERE ms.uid = ? AND ms.uid = p.code AND " +
            "UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(ms.timestamp) < ? AND msg_type = 1" +
            " UNION " +
            "SELECT DISTINCT d.code code, d.name name, d.birthday birthday, d.sex sex, d.photo photo, ms.timestamp timestamp,'doctor' type " +
            "FROM msg_statistic ms, wlyy.wlyy_doctor d, (SELECT CASE WHEN ms1.timestamp > ms2.timestamp THEN ms1.id ELSE ms2.id END id " +
            "                                                   FROM msg_statistic ms1, msg_statistic ms2 " +
            "                                                   WHERE ms1.from_gid IS NULL AND ms2.from_gid IS NULL AND ms1.uid = ms2.peer_uid AND ms1.peer_uid = ms2.uid) x " +
            "WHERE x.id = ms.id AND ((ms.uid = ? AND ms.peer_uid = d.code) OR (ms.uid = d.code AND ms.peer_uid = ?)) AND UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(ms.timestamp) < ?" +
            " UNION " +
            "SELECT g.id code, g.name name, '' birthday, '' sex, '' photo, max(ms.timestamp) timestamp, 'type' ':group' " +
            "FROM msg_statistic ms, wlyy.wlyy_admin_team g, wlyy.wlyy_admin_team_member m, wlyy.wlyy_doctor d " +
            "WHERE d.code = ? AND d.code = m.doctor_code AND m.team_id = g.id AND g.id = ms.from_gid " +
            "   AND (ms.uid = d.code or ms.from_uid = d.code) AND UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(ms.timestamp) < ? group by g.id, g.name " +
            ") x ORDER BY timestamp DESC";
        ImDb.execQuery({
            "sql": sql,
            "args": [userId, timespan, userId, userId, timespan, userId, timespan],
            "handler": handler
        });
    };
    //--------------------About group chat summary--------------------
    /**
     * 更新群聊统计摘要。
     *
     * @param userId
     * @param groupId
     * @param from
     * @param atMe
     * @param type
     * @param content
     * @param msgCountPlusOne
     * @param handler
     */
    static updateGroupChatSummary(userId, groupId, from, atMe, type, content, msgCountPlusOne, handler) {
        var uuid = userId + '_' + groupId;
        if (msgCountPlusOne) {
            ImDb.execQuery({
                "sql": "INSERT INTO msg_statistic (uid,uuid,from_uid,from_gid,at_me,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE from_uid=?,at_me=?,last_content_type=?,last_content=?,new_msg_count=new_msg_count+1",
                "args": [userId, uuid, from, groupId, atMe, 2, type, content, 1, from, atMe, type, content],
                "handler": handler
            });
        } else {
            ImDb.execQuery({
                "sql": "INSERT INTO msg_statistic (uid,uuid,from_uid,from_gid,at_me,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE from_uid=?,at_me=?,last_content_type=?,last_content=?",
                "args": [userId, uuid, from, groupId, atMe, 2, type, content, 0, from, atMe, type, content],
                "handler": handler
            });
        }
    };
    static getGroupChatAllUnReadCount(userId, handler) {
        ImDb.execQuery({
            "sql": "SELECT new_msg_count from msg_statistic WHERE uid=? AND msg_type=2 AND new_msg_count>0",
            "args": [userId],
            "handler": handler
        });
    };
    static getGroupChatSummary(userId, groupId, handler) {
        var uuid = userId + '_' + groupId;
        ImDb.execQuery({
            "sql": "SELECT uid,from_uid,from_gid,at_me,last_content_type,last_content,new_msg_count,timestamp from msg_statistic WHERE uuid = ?",
            "args": [uuid],
            "handler": handler
        });
    };
    static clearGroupChatSummaryclearGroupChatInfo(userId, groupId, handler) {
        var uuid = userId + '_' + groupId;
        ImDb.execQuery({
            "sql": "UPDATE msg_statistic SET new_msg_count='0' WHERE uuid=?",
            "args": [uuid],
            "handler": handler
        });
    };
    //--------------------Others--------------------
    static getAppMsgAmount(userId, handler) {
        ImDb.execQuery({
            "sql": "SELECT imei,token from wlyy_token WHERE user=?",
            "args": [userId],
            "handler": function (err, result) {
                if (err || result.length == 0) {
                    handler(null, 0);
                    return;
                }
                var options = {
                    hostname: config.wlyyServerConfig.host,
                    port: config.wlyyServerConfig.port,
                    path: WLYY_ENDPOINTS.Doctor.MessageCount.Path,
                    method: WLYY_ENDPOINTS.Doctor.MessageCount.Method,
                    headers: {
                        'userAgent': '{"token":"' + result[0].token + '","uid":"' + userId + '","imei":"' + result[0].imei + '"}'
                    }
                };
                var req = http.request(options, function (res) {
                    res.setEncoding('utf8');
                    log.info('请求家庭医生平台: http://', options.hostname + ":" + options.port + options.path);
                    res.on('data', function (chunk) {
                        log.info('家庭医生平台返回: ', chunk);
                        handler(null, JSON.parse(chunk));
                    });
                });
                req.on('error', function (e) {
                    log.error('家庭医生平台接口调用出错: ', e.message);
                    handler(e, null);
                });
                req.end();
            }
        });
    };
    static getBadgeNumber(userId, handler) {
        var self = this;
        async.parallel([
                function (callback) {
                    callback(null, 0);
                },
                function (callback) {
                    self.getAppMsgAmount(userId, function (err, result) {
                        if (err) {
                            callback(null, 0);
                        } else {
                            var count = 0;
                            try {
                                count += parseInt(result.data.healthIndex.amount);
                                count += parseInt(result.data.sign.amount);
                                count += parseInt(result.data.system.amount);
                                var immsg = JSON.parse(result.data.imMsgCount);
                                count += parseInt(immsg.patient);
                                count += parseInt(immsg.doctor);
                                callback(null, count);
                            } catch (e) {
                                callback(null, 0);
                            }
                        }
                    });
                }],
            function (err, results) {
                var badge = 0;
                for (var index = 0; index < results.length; index++) {
                    badge += results[index];
                }
                handler(null, badge);
            });
    };
}
module.exports = StatsRepo;

+ 6 - 1
src/server/repository/mysql/topics.repo.js

@ -54,7 +54,6 @@ class TopicRepo {
        });
    }
    /**
     * 获取会话中的议题。
     *
@ -122,6 +121,12 @@ class TopicRepo {
        });
    }
    /**
     * 更新议题状态。
     *
     * @param topicId
     * @param jsonValue
     */
    static updateTopics(topicId, jsonValue) {
        var values = [];
        let sql = "update topics set ";

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

@ -23,21 +23,7 @@ let wlyyServerConfig = {
    port: 9092
};
// 透传服务
let transServerConfig = {
    host: '172.19.103.76',
    port: 8000
};
// 企业版的推送配置
let geTuiConfig = {
    HOST: 'https://api.getui.com/apiex.htm',
    APPID: 'qWmRh2X88l7HuE36z3qBe8',
    APPKEY: 'EzERfV8c849lBkZqHWzQG1',
    MASTERSECRET: 'veXiajQrId6iojy7Qv8kZ2'
};
// AppStore版的推送App配置
// 个推AppStore版参数
let geTuiAppStoreCfg = {
    HOST: 'https://api.getui.com/apiex.htm',
    APPID: 'H6FYbDejks6VjMmW3uH7V6',
@ -75,9 +61,7 @@ exports.showSQL = true;
exports.imDbConfig = imDbConfig;
exports.redisConfig = redisConfig;
exports.geTuiConfig = geTuiConfig;
exports.geTuiAppStoreCfg = geTuiAppStoreCfg;
exports.wlyyServerConfig = wlyyServerConfig;
exports.transServerConfig = transServerConfig;
exports.wechatConfig = wechatConfig;
exports.sessionConfig = sessionConfig;

+ 1 - 17
src/server/resources/config/config.prod.js

@ -16,15 +16,7 @@ let redisConfig = {
    db: 1
};
// 企业版的推送配置
let geTuiConfig = {
    HOST: 'https://api.getui.com/apiex.htm',
    APPID: 'qWmRh2X88l7HuE36z3qBe8',
    APPKEY: 'EzERfV8c849lBkZqHWzQG1',
    MASTERSECRET: 'veXiajQrId6iojy7Qv8kZ2'
};
//AppStore版的推送App配置
// 个推AppStore版参数
let geTuiAppStoreCfg = {
    HOST: 'https://api.getui.com/apiex.htm',
    APPID: 'H6FYbDejks6VjMmW3uH7V6',
@ -38,12 +30,6 @@ let wlyyServerConfig = {
    port: 9660
};
// 透传服务
let transServerConfig = {
    host: '120.41.253.95',
    port: 3030
};
// 微信配置
let wechatConfig = {
    appId: 'wxad04e9c4c5255acf',
@ -74,9 +60,7 @@ exports.showSQL = false;
exports.imDbConfig = imDbConfig;
exports.redisConfig = redisConfig;
exports.geTuiConfig = geTuiConfig;
exports.geTuiAppStoreCfg = geTuiAppStoreCfg;
exports.wlyyServerConfig = wlyyServerConfig;
exports.transServerConfig = transServerConfig;
exports.wechatConfig = wechatConfig;
exports.sessionConfig = sessionConfig;

+ 1 - 17
src/server/resources/config/config.test.js

@ -22,21 +22,7 @@ var wlyyServerConfig = {
    port: 9092
};
// 透传服务
var transServerConfig = {
    host: '172.19.103.76',
    port: 8000
};
// 企业版的推送配置
var geTuiConfig = {
    HOST: 'https://api.getui.com/apiex.htm',
    APPID: 'qWmRh2X88l7HuE36z3qBe8',
    APPKEY: 'EzERfV8c849lBkZqHWzQG1',
    MASTERSECRET: 'veXiajQrId6iojy7Qv8kZ2'
};
//AppStore版的推送App配置
// 个推AppStore版参数
var geTuiAppStoreCfg = {
    HOST : 'https://api.getui.com/apiex.htm',
    APPID : 'H6FYbDejks6VjMmW3uH7V6',
@ -70,9 +56,7 @@ exports.showSQL= true;
exports.imDbConfig = imDbConfig;
exports.redisConfig = redisConfig;
exports.geTuiConfig = geTuiConfig;
exports.geTuiAppStoreCfg = geTuiAppStoreCfg;
exports.wlyyServerConfig = wlyyServerConfig;
exports.transServerConfig = transServerConfig;
exports.wechatConfig = wechatConfig;
exports.sessionConfig = sessionConfig;

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

@ -152,6 +152,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表示临时讨论组',
	`business_type` INT COMMENT '业务相关类型',
	`create_date` DATETIME(0) NOT NULL COMMENT '创建时间',
	`last_sender_id` VARCHAR(50) COMMENT '消息最后发送人ID',
	`last_sender_name` VARCHAR(50) COMMENT '消息最后发送人姓名',

+ 5 - 1
src/server/resources/schema/ichat_1.2.8_view_schema.sql

@ -10,4 +10,8 @@ from wlyy.wlyy_patient;
create or replace view wechat_status as
select code user_id, openid open_id
from wlyy.wlyy_patient
;
;
ALTER TABLE `wlyy_consult_team`
MODIFY COLUMN `end_msg_id`  varchar(50) NULL DEFAULT NULL AFTER `guidance`,
MODIFY COLUMN `start_msg_id`  varchar(50) NULL DEFAULT NULL AFTER `end_msg_id`;

+ 15 - 21
src/server/resources/schema/temp.sql

@ -1,6 +1,6 @@
/* ---------------------------------------------------- */
/*  Generated by Enterprise Architect Version 12.0 		*/
/*  Created On : 05-Jan-2017 9:17:28 AM 				*/
/*  Created On : 06-Jan-2017 12:02:36 PM 				*/
/*  DBMS       : MySql 						*/
/* ---------------------------------------------------- */
@ -8,31 +8,25 @@ SET FOREIGN_KEY_CHECKS=0
/* Drop Tables */
DROP TABLE IF EXISTS `topics` CASCADE
DROP TABLE IF EXISTS `sessions` CASCADE
;
/* Create Tables */
CREATE TABLE `topics`
CREATE TABLE `sessions`
(
	`id` VARCHAR(32) NOT NULL COMMENT 'ID',
	`session_id` VARCHAR(50) NOT NULL COMMENT 'MUC会话ID',
	`name` VARCHAR(50) COMMENT '议题名称',
	`create_time` TIMESTAMP(0) COMMENT '创建时间',
	`end_by` VARCHAR(50) COMMENT '结束人ID',
	`end_time` TIMESTAMP(0) COMMENT '结束时间',
	`start_message_id` INTEGER COMMENT '消息起始ID',
	`end_message_id` INTEGER COMMENT '消息结束ID',
	`status` INT COMMENT '议题状态,0新建,1已回复未结束,10结束',
	`description` VARCHAR(1024) COMMENT '议题描述',
	CONSTRAINT `PK_topics` PRIMARY KEY (`id`)
) COMMENT='议题,仅MUC模式使用。'
;
/* Create Primary Keys, Indexes, Uniques, Checks */
ALTER TABLE `topics` 
 ADD INDEX `IXFK_topics_sessions` (`session_id` ASC)
	`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表示临时讨论组',
	`business_type` INT COMMENT '业务相关类型',
	`create_date` DATETIME(0) NOT NULL COMMENT '创建时间',
	`last_sender_id` VARCHAR(50) COMMENT '消息最后发送人ID',
	`last_sender_name` VARCHAR(50) COMMENT '消息最后发送人姓名',
	`last_content_type` VARCHAR(50) COMMENT '消息最后内容类型',
	`last_content` VARCHAR(1024) COMMENT '消息最后内容',
	`last_message_time` TIMESTAMP(0) COMMENT '消息最后时间',
	CONSTRAINT `PK_sessions` PRIMARY KEY (`id`)
) COMMENT='会话'
;
SET FOREIGN_KEY_CHECKS=1

+ 138 - 0
src/server/util/wechat.sdk.js

@ -0,0 +1,138 @@
/**
 * 简易微信SDK。
 *
 * author: Sand
 * since: 1/6/2017
 */
"use strict";
let WechatTokenRepo = require('../repository/mysql/wechat.token.repo.js');
let configFile = require('../include/commons').CONFIG_FILE;
let config = require('../resources/config/' + configFile);
let log = require('./log');
let https = require('https');
class WechatSDK {
    constructor() {
    }
    /**
     * 获取微信access_token
     */
    static getAccessToken(handler) {
        WechatTokenRepo.findOne(function (err, result) {
            if (err) {
                handler(err, null);
                return;
            }
            var data = result && result.length > 0 ? result[0] : null;
            var accessToken = "";
            // 判断access_token是否有效
            if (data) {
                if ((new Date().getTime() - data.add_timestamp) < (data.expires_in * 1000)) {
                    accessToken = data.access_token;
                }
            }
            // access_token为空时从微信新获取并执行回调,否则直接执行回调
            if (!accessToken) {
                var basePath = "https://api.weixin.qq.com/cgi-bin/token?";
                var params = "grant_type=client_credential&appid=" + config.wechatConfig.appId
                    + "&secret=" + config.wechatConfig.appSecret;
                https.get(basePath + params, function (res) {
                    var data = '';
                    res.on('data', (d) => {
                        data += d;
                    });
                    res.on('end', () => {
                        data = data ? JSON.parse(data) : {};
                        if (data.access_token) {
                            accessToken = data.access_token;
                            WechatTokenRepo.save(accessToken, data.expires_in, new Date(), function (err, result) {
                                if (err) {
                                    if (handler) handler(err, null);
                                } else {
                                    if (handler) handler(null, accessToken);
                                }
                            });
                        } else {
                            if (handler) handler(Error("Get access_token from wechat failed"), null);
                        }
                    });
                }).on('error', (e) => {
                    if (handler) handler(Error("Get access_token from wechat failed: " + e.message), null);
                });
            } else {
                if (handler) handler(data, null);
            }
        });
    };
    /**
     * 发送微信模板消息
     */
    static sendTemplateMessage(message, handler) {
        WechatSDK.getAccessToken(function (err, token) {
            if (err) {
                log.error(err);
                handler(err, null);
                return;
            }
            // 发送模板消息
            var messageJSON = JSON.stringify(message);
            var request = https.request({
                    host: 'api.weixin.qq.com',
                    path: '/cgi-bin/message/template/send?access_token=' + token,
                    method: 'POST'
                },
                function (res) {
                    res.setEncoding('utf8');
                    var data = "";
                    res.on('data', (d) => {
                        data += d;
                    });
                    res.on('end', () => {
                        var result = JSON.parse(data);
                        if (result && result.errcode === 0) {
                            log.info("send wechat template message success:" + messageJSON);
                            if (handler) {
                                handler(null, result);
                            }
                        } else {
                            log.error("send wechat template message failed:" + messageJSON);
                            if (handler) {
                                handler(result, null);
                            }
                        }
                    });
                    res.on('error', function (err) {
                        log.error('send wechat template message failed: ' + err.message);
                        if (handler) {
                            handler(err, null);
                        }
                    });
                })
                .on('error', (e) => {
                    log.error("send wechat template message failed:" + e.message);
                    if (handler) {
                        handler(e, null);
                    }
                });
            request.end(messageJSON);
        });
    };
}
module.exports = WechatSDK;

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

@ -160,6 +160,52 @@ describe("Session P2P", function () {
        });
    });
    // 获取会话的消息
    describe("Get session messages", function () {
        it("should return session messages by page", function (done) {
            imClient.Sessions.getMessagesBySession(TD.SessionId, TD.DoctorB.id, null, null, null, null,
            function (messages) {
                assert.ok(messages.length > 0);
            },
            function (xhr, status, error) {
                assert.ok(false, message.responseJSON.message);
            });
        });
    });
    // 获取会话成员及头像
    describe("Get session participants", function () {
        it("should return two participants and avatars", function (done) {
            imClient.Sessions.getParticipants(TD.SessionId,
                function (data) {
                    assert.ok(data.length, 2, "P2P session participant number must be 2");
                    data.forEach(function (participant) {
                        console.log(participant);
                    });
                },
                function (xhr, status, error) {
                    assert.ok(false, xhr.responseJSON.message);
                });
            imClient.Sessions.getParticipantsAvatars(TD.SessionId,
                function (avatars) {
                    assert.ok(avatars.length, 2, "P2P session participant avatars number must be 2");
                    avatars.forEach(function (avatars) {
                        console.log(avatars);
                    });
                    done();
                },
                function (xhr, status, error) {
                    assert.ok(false, xhr.responseJSON.message);
                    done();
                });
        });
    });
    // 退出
    describe("User logout", function () {
        it("all user must be success", function (done) {