Bladeren bron

重构搜索API

Sand 8 jaren geleden
bovenliggende
commit
550f93555b

+ 67 - 30
src/client/im.client.js

@ -77,7 +77,7 @@ var ENDPOINTS = {
        ParticipantAvatar: '/session/:session_id/participants/:participant_id/avatars'
    },
    Search: {
        Search: "/search"
        Search: '/search'
    }
};
@ -399,39 +399,76 @@ var imClient = {
                {user_id: userId, date_span: 7},
                success,
                failure);
        }
    },
    Search: {
        // 搜索所有对象:医生,会话及消息
        searchAll: function (userId, keyword, success, failure) {
            httpClient.get(ENDPOINTS.Search.Search,
                {user_id: userId, target: 'all', keyword: keyword},
                success,
                failure);
        },
        // 搜索医生
        searchDoctors: function (userId, keyword, page, size, success, failure) {
            httpClient.get(ENDPOINTS.Search.Search,
                {user_id: userId, target: 'doctor', keyword: keyword, page: page, size: size},
                success,
                failure);
        },
        Search: {
            Doctor: {
                // 搜索所有对象:医生,会话及消息
                searchAll: function (userId, keyword, success, failure) {
                    httpClient.get(ENDPOINTS.Search.Search,
                        {user_id: userId, target_role: 'doctor', entity_type: 'all', keyword: keyword, exclude_topic_ended_sessions: false},
                        success,
                        failure);
                },
        // 搜索会话
        searchSessions: function (userId, keyword, page, size, success, failure) {
            httpClient.get(ENDPOINTS.Search.Search,
                {user_id: userId, target: 'session', keyword: keyword, page: page, size: size},
                success,
                failure);
        },
                // 搜索医生
                searchDoctors: function (userId, keyword, page, size, success, failure) {
                    httpClient.get(ENDPOINTS.Search.Search,
                        {user_id: userId, target_role: 'doctor', entity_type: 'user', keyword: keyword, exclude_topic_ended_sessions: false, page: page, size: size},
                        success,
                        failure);
                },
        // 搜索会话消息
        searchMessages: function (userId, keyword, page, size, success, failure) {
            httpClient.get(ENDPOINTS.Search.Search,
                {user_id: userId, target: 'message', keyword: keyword, page: page, size: size},
                success,
                failure);
                // 搜索会话
                searchSessions: function (userId, keyword, page, size, success, failure) {
                    httpClient.get(ENDPOINTS.Search.Search,
                        {user_id: userId, target_role: 'doctor', entity_type: 'session', keyword: keyword, exclude_topic_ended_sessions: false, page: page, size: size},
                        success,
                        failure);
                },
                // 搜索会话消息
                searchMessages: function (userId, keyword, page, size, success, failure) {
                    httpClient.get(ENDPOINTS.Search.Search,
                        {user_id: userId, target_role: 'doctor', entity_type: 'message', keyword: keyword, exclude_topic_ended_sessions: false, page: page, size: size},
                        success,
                        failure);
                }
            },
            Patient: {
                // 搜索所有对象:患者,会话及消息
                searchAll: function (userId, keyword, success, failure) {
                    httpClient.get(ENDPOINTS.Search.Search,
                        {user_id: userId, target_role: 'patient', entity_type: 'all', keyword: keyword, exclude_topic_ended_sessions: false},
                        success,
                        failure);
                },
                // 搜索患者
                searchDoctors: function (userId, keyword, page, size, success, failure) {
                    httpClient.get(ENDPOINTS.Search.Search,
                        {user_id: userId, target_role: 'patient', entity_type: 'user', keyword: keyword, exclude_topic_ended_sessions: false, page: page, size: size},
                        success,
                        failure);
                },
                // 搜索会话
                searchSessions: function (userId, keyword, page, size, success, failure) {
                    httpClient.get(ENDPOINTS.Search.Search,
                        {user_id: userId, target_role: 'patient', entity_type: 'session', keyword: keyword, exclude_topic_ended_sessions: false, page: page, size: size},
                        success,
                        failure);
                },
                // 搜索会话消息
                searchMessages: function (userId, keyword, page, size, success, failure) {
                    httpClient.get(ENDPOINTS.Search.Search,
                        {user_id: userId, target_role: 'patient', entity_type: 'message', keyword: keyword, exclude_topic_ended_sessions: false, page: page, size: size},
                        success,
                        failure);
                }
            }
        }
    }
};

+ 16 - 19
src/server/endpoints/v2/search.endpoint.js

@ -10,46 +10,43 @@ let express = require('express');
let router = express.Router();
let ControllerUtil = require("../../util/controller.util.js");
let MySqlSearcher = require('../../models/search/mysql.searcher');
let ObjectSearcher = require('../../models/search/object.searcher');
const APIv2 = require('../../include/endpoints').APIv2;
/**
 * 搜索API。
 * 搜索。
 *
 * 参数:
 *  target: all/doctor/session/message
 *  user_id: 用户ID
 *  target_role: 目标角色,patient为患者相关,doctor为医生相关
 *  entity_type: 实体类型,user为用户,session为会话,message为消息,all为全部都搜索
 *  keyword: 关键字
 *  exclude_topic_ended_sessions: 排除议题已结束的会话
 *  page: 第几页,从1开始,当target为all时无效
 *  size: 页大小,当target为all时无效
 *
 * URL:
 *  /search?target=all&keyword=张&page
 *  /search?user_id=e51ac&target_role=doctor&entity_type=all&keyword=张&exclude_topic_ended_sessions=true&page=1&size=3
 */
router.get("/", function (req, res) {
    ControllerUtil.checkRequestQueryParams(req, ['user_id', 'target', 'keyword']);
router.get('/', function (req, res) {
    ControllerUtil.checkRequestQueryParams(req, ['user_id', 'target_role', 'entity_type', 'keyword']);
    let userId = req.query.user_id;
    let target = req.query.target;
    let targetRole = req.query.target_role;
    let entityType = req.query.entity_type;
    let keyword = req.query.keyword;
    let excludeTopicEndedSessions = req.query.exclude_topic_ended_sessions === 'true';
    let page = req.query.page;
    let size = req.query.size;
    page = page ? parseInt(page) - 1 : 0;
    size = size ? parseInt(size) : 10;
    let searcher = new MySqlSearcher();
    let searcher = new ObjectSearcher();
    ControllerUtil.regModelEventHandler(searcher, res);
    if (target == 'all') {
        searcher.searchAll(userId, keyword);
    } else if (target == 'doctor') {
        searcher.searchDoctors(keyword, page, size)
    } else if (target == 'session') {
        searcher.searchSessions(userId, keyword, page, size);
    } else if (target == 'message') {
        searcher.searchMessages(userId, keyword, page, size);
    } else {
        throw {message: "Unknown target type: " + target};
    }
    searcher.search(userId, keyword, targetRole, entityType, excludeTopicEndedSessions, page, size);
});
module.exports = router;

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

@ -170,6 +170,8 @@ exports.REDIS_KEYS = {
};
const DB_TABLES = {
    Doctors: "doctors",
    Patients: "patients",
    P2pMessages: "p2p_messages",
    MucMessages: "muc_messages",
    GroupMessages: "group_messages",

+ 1 - 1
src/server/include/endpoints.js

@ -50,7 +50,7 @@ const APIv2 = {
        ParticipantAvatar: '/:session_id/participants/:participant_id/avatars'  // 会话单个成员头像
    },
    Search: {
        Base: '/api/v2/search'                                          // 搜索,下一版本的语法使用ElasticSearch
        Base: '/api/v2/search'                                         // 搜索,下一版本的语法使用ElasticSearch
    }
};

+ 0 - 171
src/server/models/search/mysql.searcher.js

@ -1,171 +0,0 @@
/**
 * 用户、会话及消息搜索。
 *
 * 注意:此模型效率堪忧,但为了实现先这样做。更稳妥的方案是使用Solr或Elastic Search
 * 为数据提供索引功能,JS使用搜索接口搜索之后再取得对象的ID进行获取,提高效率。
 * 后续开发都希望看到这段注释,实现此方案。
 *
 * author: Sand
 * since: 2016.11.20
 */
"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.js");
let async = require("async");
class MySqlSearcher extends BaseModel {
    constructor() {
        super();
    }
    /**
     * 一次性搜索所有对象。
     *
     * @param userId
     * @param keyword
     */
    searchAll(userId, keyword) {
        let self = this;
        async.waterfall([
                function (callback) {
                    self.searchDoctors(keyword, 0, 4, function (err, doctors) {
                        if (err) return callback(err, null);
                        let data = {};
                        let buffer = [];
                        doctors.forEach(function (doctor) {
                            buffer.push(doctor);
                        });
                        data.doctor = buffer;
                        callback(null, data);
                    });
                },
                function (data, callback) {
                    self.searchSessions(userId, keyword, 0, 4, function (err, sessions) {
                        if (err) return callback(err, null);
                        let buffer = [];
                        sessions.forEach(function (session) {
                            buffer.push(session);
                        });
                        data.groups = buffer;
                        callback(null, data);
                    });
                },
                function (data, callback) {
                    self.searchMessages(userId, keyword, 0, 4, function (err, sessions) {
                        if (err) return callback(err, null);
                        let buffer = [];
                        sessions.forEach(function (session) {
                            buffer.push(session);
                        });
                        data.content = buffer;
                        callback(null, data);
                    });
                }
            ],
            function (err, res) {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, "Search all object types failed", err);
                } else {
                    ModelUtil.emitOK(self.eventEmitter, res);
                }
            });
    }
    /**
     * 医生搜索。
     *
     * @param keyword
     * @param page
     * @param size
     * @param handler
     */
    searchDoctors(keyword, page, size, handler) {
        let self = this;
        SearchRepo.searchDoctors(keyword, page, size, function (err, doctors) {
            if (handler) {
                handler(err, doctors);
            } else {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, "Search doctors failed", err);
                } else {
                    doctors.forEach(function (doctor) {
                        doctor.avatar = doctor.avatar ? doctor.avatar : "";
                        doctor.birthdate = doctor.birthdate ? doctor.birthdate : "";
                    });
                    ModelUtil.emitOK(self.eventEmitter, doctors);
                }
            }
        });
    }
    /**
     * 会话搜索。
     *
     * @param userId
     * @param keyword
     * @param page
     * @param size
     * @param handler
     */
    searchSessions(userId, keyword, page, size, handler) {
        let self = this;
        SearchRepo.searchSessions(userId, keyword, page, size, function (err, sessions) {
            if (handler) {
                handler(err, sessions);
            } else {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, "Search sessions failed", err);
                } else {
                    sessions.forEach(function (session) {
                        session.create_date = ObjectUtil.timestampToLong(session.create_date);
                    });
                    ModelUtil.emitOK(self.eventEmitter, sessions);
                }
            }
        });
    }
    /**
     * 搜索会话消息。
     *
     * @param userId
     * @param keyword
     * @param page
     * @param size
     * @param handler
     */
    searchMessages(userId, keyword, page, size, handler) {
        let self = this;
        SearchRepo.searchMessages(userId, keyword, page, size, function (err, messages) {
            if (handler) {
                handler(err, messages);
            } else {
                if (err) {
                    ModelUtil.emitError(self.eventEmitter, "Search sessions failed", err);
                } else {
                    messages.forEach(function (message) {
                        message.create_time = ObjectUtil.timestampToLong(message.create_time);
                    });
                    ModelUtil.emitOK(self.eventEmitter, messages);
                }
            }
        });
    }
}
module.exports = MySqlSearcher;

+ 89 - 0
src/server/models/search/object.searcher.js

@ -0,0 +1,89 @@
"use strict";
let RedisModel = require('./../redis.model.js');
let ModelUtil = require('../../util/model.util');
let RedisClient = require('../../repository/redis/redis.client.js');
let SearchRepo = require('../../repository/mysql/search.repo');
let log = require('../../util/log.js');
let async = require("async");
let redis = RedisClient.redisClient().connection;
const REDIS_KEYS = require('../../include/commons').REDIS_KEYS;
const DB_TABLES = require('../../include/commons').DB_TABLES;
class ObjectSearcher extends RedisModel {
    constructor() {
        super();
    }
    search(userId, keyword, targetRole, entityType, excludeTopicEndedSessions, page, size) {
        let self = this;
        async.waterfall([
                // 获取会话ID列表,根据参数过滤应该议题已结束的会话
                function (callback) {
                    if (excludeTopicEndedSessions) {
                        SearchRepo.findTopicEndedSessionIdList(userId, function (err, sessionIdList) {
                            if (err) return callback(err, null);
                            callback(null, sessionIdList);
                        })
                    } else {
                        redis.zrangeAsync(RedisModel.makeRedisKey(REDIS_KEYS.UserSessions, userId), 0, -1)
                            .then(function (sessionIdList) {
                                callback(null, sessionIdList, keyword, targetRole, entityType);
                            })
                            .catch(function (err) {
                                return callback(err, null);
                            });
                    }
                },
                // 搜索
                function (sessionIdList, keyword, targetRole, entityType, callback) {
                    if (targetRole !== 'doctor' && targetRole !== 'patient') {
                        ModelUtil.emitError(self.eventEmitter, "Unknown target role: " + targetRole);
                    }
                    let userTable = targetRole === 'doctor' ? DB_TABLES.Doctors : DB_TABLES.Patients;
                    if (entityType === 'all') {
                        SearchRepo.searchAll(sessionIdList, keyword, userTable, function (err, res) {
                            if (err) return callback(err, null);
                            callback(null, res);
                        });
                    } else if (entityType === 'user') {
                        SearchRepo.searchUser(sessionIdList, keyword, userTable, page, size, function (err, res) {
                            if (err) return callback(err, null);
                            callback(null, res);
                        });
                    } else if (entityType === 'session') {
                        SearchRepo.searchSessions(sessionIdList, keyword, page, size, function (err, res) {
                            if (err) return callback(err, null);
                            callback(null, res);
                        });
                    } else if (entityType === 'message') {
                        SearchRepo.searchMessages(sessionIdList, keyword, page, size, function (err, res) {
                            if (err) return callback(err, null);
                            callback(null, res);
                        });
                    } else {
                        callback(new Error("Unknown entity type: " + entityType), null);
                    }
                }
            ],
            function (err, res) {
                if (err) {
                    return ModelUtil.emitError(self.eventEmitter, "Search failed", err);
                }
                ModelUtil.emitOK(self.eventEmitter, res);
            });
    }
}
module.exports = ObjectSearcher;

+ 1 - 1
src/server/repository/mysql/app.status.repo.js

@ -9,7 +9,7 @@
 */
"use strict";
var ImDb = require("./db/im.db.js");
let ImDb = require("./db/im.db.js");
class AppStatusRepo {
    constructor() {

+ 100 - 46
src/server/repository/mysql/search.repo.js

@ -1,81 +1,135 @@
/**
 * 搜索。
 */
"use strict";
'use strict';
let ImDb = require('../mysql/db/im.db');
let async = require("async");
const DB_TABLES = require('../../include/commons').DB_TABLES;
class SearchRepo {
    constructor() {
    }
    static searchDoctors(keyword, page, size, handler) {
        if(page<=0)page = 1;
        page = (page-1)*size;
        let sql = "select d.id, d.name, d.sex, d.birthdate, d.avatar, d.level from doctors d where d.name like ? limit ?, ?";
    /**
     * 查询正常会话及议题已结束的会话(P2P, MUC)
     *
     * @param userId
     * @param handler
     */
    static findTopicEndedSessionIdList(userId, handler) {
        let sql = "SELECT s.id " +
            "FROM sessions s, topics t, participants p " +
            "WHERE p.participant_id = '' AND p.session_id = s.id AND s.id = t.session_id AND t.end_message_id IS NOT NULL AND s.`type` IN (1) " +
            "UNION " +
            "SELECT s.id " +
            "FROM sessions s, participants p " +
            "WHERE p.participant_id = '' AND p.session_id = s.id AND s.`type` IN (2, 3)";
        ImDb.execQuery({
            sql: sql,
            args: ['%' + keyword + '%',page , size],
            args: [userId, userId],
            handler: handler
        });
        })
    }
    /**
     * 搜索会话。
     * 全部搜索
     *
     * TODO: 还需要联合搜索包含特定用户的会话。当前只实现了按会话名称搜索。
     * @param sessionIdList
     * @param keyword
     * @param userTable
     * @param handler
     */
    static searchAll(sessionIdList, keyword, userTable, handler) {
        async.waterfall([
            function (callback) {
                SearchRepo.searchUser(sessionIdList, keyword, userTable, 0, 3, function (err, res) {
                    if (err) return handler(err, null);
                    let data = {};
                    if (userTable == DB_TABLES.Doctors) {
                        data.doctors = res;
                    } else {
                        data.patients = res;
                    }
                    callback(null, data);
                });
            },
            function (data, callback) {
                SearchRepo.searchSessions(sessionIdList, keyword, 0, 3, function (err, res) {
                    if (err) return handler(err, null);
                    data.sessions = res;
                    callback(null, data);
                })
            },
            function (data, callback) {
                SearchRepo.searchMessages(sessionIdList, keyword, 0, 3, function (err, res) {
                    if (err) return handler(err, null);
                    data.messages = res;
                    handler(null, data);
                })
            }
        ]);
    }
    /**
     * 用户搜索
     *
     * @param userId
     * @param sessionIdList
     * @param keyword
     * @param userTable
     * @param page
     * @param size
     * @param handler
     */
    static searchSessions(userId, keyword, page, size, handler) {
        let sql = "select s.id, s.name, s.type, s.create_date, s.business_type," +
            "(SELECT group_concat(d.name) FROM doctors d,participants p1 WHERE p1.participant_id = d.id and p1.session_id = s.id and d.name like ?) as members," +
            "(SELECT group_concat(d.avatar) FROM doctors d,participants p1 WHERE p1.participant_id = d.id and p1.session_id = s.id) as images from sessions s" +
            " where s.id in (select id from sessions s, participants p where s.id = p.session_id and p.participant_id = ?) " +
            "and s.type <> 0 and s.name like ? limit ?, ?";
    static searchUser(sessionIdList, keyword, userTable, page, size, handler) {
        let sql = "select u.id, u.name, u.sex, u.avatar from sessions s, participants p, " + userTable + " u where s.id in ('" +
            sessionIdList.join("', '") +
            "') AND s.id = p.session_id AND p.participant_id = u.id and u.name like ? limit ?, ?";
        keyword = '%' + keyword + '%';
        ImDb.execQuery({
            sql: sql,
            args: [ '%' + keyword + '%',userId, '%' + keyword + '%', page * size, size],
            args: [keyword, page * size, size],
            handler: handler
        });
    }
    /**
     * 搜索会话消息。
     *
     * @param userId
     * @param keyword
     * @param page
     * @param size
     * @param handler
     * 会话搜索
     */
    static searchSessions(sessionIdList, keyword, page, size, handler) {
        let sql = "select s.id, s.name, s.type, s.create_date, s.business_type from sessions s where s.id in ('" +
            sessionIdList.join("', '") +
            "') and s.name like ? limit ?, ? ";
        keyword = '%' + keyword + '%';
        ImDb.execQuery({
            sql: sql,
            args: [keyword, page * size, size],
            handler: handler
        });
    }
    /**
     * 消息搜索
     */
    static searchMessages(userId, keyword, page, size, handler) {
        let sql = "SELECT * FROM( " +
        "SELECT s.id session_id, s.name session_name, s.type session_type, s.create_date, s.business_type, count(m.id) count, max(m.content) message_content " +
        "FROM sessions s, participants p, muc_messages m " +
        "WHERE p.participant_id = ? and p.session_id = s.id and s.id = m.session_id and m.content_type = 1 AND m.content LIKE ? " +
        "GROUP BY s.id, s.name, s.type, s.create_date, s.business_type "+
         "UNION " +
        "SELECT s.id session_id, s.name session_name, s.type session_type, s.create_date, s.business_type, count(m.id) count, max(m.content) message_content " +
        "FROM sessions s, participants p, group_messages m " +
        "WHERE p.participant_id = ? and p.session_id = s.id and s.id = m.session_id and m.content_type = 1 AND m.content LIKE ? " +
        "GROUP BY s.id, s.name, s.type, s.create_date, s.business_type "+
        "UNION " +
        "SELECT s.id session_id, s.name session_name, s.type session_type, s.create_date, s.business_type, count(m.id) count, max(m.content) message_content " +
        "FROM sessions s, participants p, p2p_messages m " +
        "WHERE p.participant_id = ? and p.session_id = s.id and s.id = m.session_id and m.content_type = 1 AND m.content LIKE ? " +
         "GROUP BY s.id, s.name, s.type, s.create_date, s.business_type "+
         ") X LIMIT ?, ?";
    static searchMessages(sessionIdList, keyword, page, size, handler) {
        let sql = "SELECT s.id, s.name, s.type, s.create_date, s.business_type, m.sender_name, m.content " +
            "FROM sessions s, p2p_messages m " +
            "WHERE s.id IN('" + sessionIdList.join("', '") + "') AND s.id = m.session_id AND s.`type` IN (1,2,3) AND m.content_type = 1 AND m.content LIKE ? " +
            "GROUP BY s.id " +
            "ORDER BY s.last_message_time " +
            "LIMIT ?, ?";
        keyword = '%' + keyword + '%';
        ImDb.execQuery({
            sql: sql,
            args: [userId, keyword, userId, keyword, userId, keyword, page * size, size],
            args: [keyword, page * size, size],
            handler: handler
        });
    }

+ 7 - 7
src/server/resources/config/config.dev.js

@ -24,20 +24,20 @@ let wlyyServerConfig = {
};
// 个推AppStore版参数
/*let getTuiConfig = {
let getTuiConfig = {
    HOST: 'https://api.getui.com/apiex.htm',
    APPID: 'H6FYbDejks6VjMmW3uH7V6',
    APPKEY: '0PFWlKmLBN9YzhCfFWVgYA',
    MASTERSECRET: 'pvjCGtRZJx9SRVODkxc816'
};*/
    APPID: 'qWmRh2X88l7HuE36z3qBe8',
    APPKEY: 'EzERfV8c849lBkZqHWzQG1',
    MASTERSECRET: 'veXiajQrId6iojy7Qv8kZ2'
};
let getTuiConfig = {
/*let getTuiConfig = {
    HOST: 'https://api.getui.com/apiex.htm',
    APPID: 'DKgbGvbacm74nJJzen5ilA',
    APPSECRET: '4kGcL7e7kU6mbSqfEGZFW7',
    APPKEY: 'ArZfS2qvoA7N3hawOAGVC5',
    MASTERSECRET: '9lpy5vEss46tVzP1RCJiC4'
};
};*/
// 微信配置
let wechatConfig = {

+ 3 - 3
src/server/resources/config/config.prod.js

@ -25,9 +25,9 @@ let wlyyServerConfig = {
// 个推AppStore版参数
let getTuiConfig = {
    HOST: 'https://api.getui.com/apiex.htm',
    APPID: 'H6FYbDejks6VjMmW3uH7V6',
    APPKEY: '0PFWlKmLBN9YzhCfFWVgYA',
    MASTERSECRET: 'pvjCGtRZJx9SRVODkxc816'
    APPID: 'qWmRh2X88l7HuE36z3qBe8',
    APPKEY: 'EzERfV8c849lBkZqHWzQG1',
    MASTERSECRET: 'veXiajQrId6iojy7Qv8kZ2'
};
// 微信配置

+ 4 - 4
src/server/resources/config/config.test.js

@ -24,10 +24,10 @@ let wlyyServerConfig = {
// 个推AppStore版参数
let getTuiConfig = {
    HOST : 'https://api.getui.com/apiex.htm',
    APPID : 'H6FYbDejks6VjMmW3uH7V6',
    APPKEY : '0PFWlKmLBN9YzhCfFWVgYA',
    MASTERSECRET : 'pvjCGtRZJx9SRVODkxc816'
    HOST: 'https://api.getui.com/apiex.htm',
    APPID: 'qWmRh2X88l7HuE36z3qBe8',
    APPKEY: 'EzERfV8c849lBkZqHWzQG1',
    MASTERSECRET: 'veXiajQrId6iojy7Qv8kZ2'
};
// 微信配置

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

@ -44,7 +44,7 @@ class ModelUtil {
    static emitError(eventEmitter, description, err) {
        ModelUtil.logError(description, err);
        eventEmitter.emit(MODEL_EVENTS.Error, {message: description});
        eventEmitter.emit(MODEL_EVENTS.Error, {message: description + ": " + err});
    };
    /**

+ 80 - 0
test/client/im.client.doctor.session.search.Test.js

@ -0,0 +1,80 @@
"use strict";
let assert = require('assert');
let imClient = require('../../src/client/im.client');
let userId = 'D2016008240002';
describe('API: Doctor Search', function () {
    // 搜索所有
    describe('search all object types', function () {
        it('should return 200', function (done) {
            imClient.Sessions.Search.Doctor.searchAll(userId, '哦',
            function (data) {
                assert(data.messages.length > 0, "Search must return at least one data");
                console.log(data);
                done();
            },
            function (xhr, status, error) {
                assert.ok(false, xhr.responseJSON.message);
                done();
            });
        });
    });
    // 搜索医生
    describe('search doctor', function () {
        it('should return 200', function (done) {
            imClient.Sessions.Search.Doctor.searchDoctors(userId, '张', 1, 10,
                function (data) {
                    assert(data.length > 0, "Search must return at least one data");
                    console.log(data);
                    done();
                },
                function (xhr, status, error) {
                    assert.ok(false, xhr.responseJSON.message);
                    done();
                });
        });
    });
    // 搜索会话
    describe('search sessions', function () {
        it('should return 200', function (done) {
            imClient.Sessions.Search.Doctor.searchSessions('D2016008240003', '丽', 1, 10,
                function (data) {
                    assert(data.length > 0, "Search must return at least one data");
                    console.log(data);
                    done();
                },
                function (xhr, status, error) {
                    assert.ok(false, xhr.responseJSON.message);
                    done();
                });
        });
    });
    // 搜索会话消息
    describe('search messages', function () {
        it('should return 200', function (done) {
            imClient.Sessions.Search.Doctor.searchMessages(userId, '哦', 1, 10,
                function (data) {
                    assert(data.length > 0, "Search must return at least one data");
                    console.log(data);
                    done();
                },
                function (xhr, status, error) {
                    assert.ok(false, xhr.responseJSON.message);
                    done();
                });
        });
    });
});

+ 6 - 6
test/client/im.client.search.Test.js

@ -5,11 +5,11 @@ let imClient = require('../../src/client/im.client');
let userId = 'D2016008240002';
describe('API: Search', function () {
describe('API: Patient Search', function () {
    // 搜索所有
    describe('search all object types', function () {
        it('should return 200', function (done) {
            imClient.Search.searchAll(userId, '哦',
            imClient.Sessions.Search.Patient.searchAll(userId, '哦',
            function (data) {
                assert(data.length > 0, "Search must return at least one data");
@ -24,10 +24,10 @@ describe('API: Search', function () {
        });
    });
    // 搜索医生
    // 搜索患者
    describe('search doctors', function () {
        it('should return 200', function (done) {
            imClient.Search.searchDoctors(userId, '张', 1, 10,
            imClient.Sessions.Search.Patient.searchPatients(userId, '张', 1, 10,
                function (data) {
                    assert(data.length > 0, "Search must return at least one data");
@ -45,7 +45,7 @@ describe('API: Search', function () {
    // 搜索会话
    describe('search sessions', function () {
        it('should return 200', function (done) {
            imClient.Search.searchSessions('D2016008240003', '1', 1, 10,
            imClient.Sessions.Search.Patient.searchSessions('D2016008240003', '1', 1, 10,
                function (data) {
                    assert(data.length > 0, "Search must return at least one data");
@ -63,7 +63,7 @@ describe('API: Search', function () {
    // 搜索会话消息
    describe('search messages', function () {
        it('should return 200', function (done) {
            imClient.Search.searchMessages(userId, '哦', 1, 10,
            imClient.Sessions.Search.Patient.searchMessages(userId, '哦', 1, 10,
                function (data) {
                    assert(data.length > 0, "Search must return at least one data");