Jelajahi Sumber

增加计划任务调度;完成议题超过指定时间自动关闭逻辑;增加议题自动关闭配置;增加用户token视图;数据库SQL执行失败时,输出错误SQL参数;封装WLYY调用SDK

Sand 8 tahun lalu
induk
melakukan
be1777f1f0

+ 4 - 1
src/server/app.js

@ -20,6 +20,7 @@ let config = require('./resources/config/' + configFile);
// handlers
let SocketHandler = require('./handlers/socket.handler');
let UrlInitializer = require('./endpoints/url.initializer');
let JobInitializer = require('./models/schedule/job.initializer');
// initialize express application
let app = express();
@ -109,4 +110,6 @@ if(!server.address()){
} else {
    log.info('Starting IM server, version ' + config.version + ', running on port ' + server.address().port + ', ' + new Date().toLocaleString());
    log.info('Configuration profile: ' + configFile.split('.')[1]);
}
}
JobInitializer.init();

+ 8 - 30
src/server/endpoints/v2/application.endpoint.js

@ -7,6 +7,7 @@ let http = require('http');
let configFile = require('../../include/commons').CONFIG_FILE;
let config = require('../../resources/config/' + configFile);
let ImDb = require('../../repository/mysql/db/im.db');
let WlyySDK = require("../../util/wlyy.sdk");
let log = require('../../util/log.js');
const APIv2 = require('../../include/endpoints').APIv2;
@ -25,45 +26,22 @@ const APIv2 = require('../../include/endpoints').APIv2;
router.get(APIv2.Application.BadgeNo, function (req, response) {
    let userId = req.query.user_id;
    ImDb.execQuery({
        "sql": "SELECT imei,token from wlyy.wlyy_token WHERE user=?",
        "sql": "SELECT imei, token from user_tokens WHERE user = ?",
        "args": [userId],
        "handler": function (err, result) {
            if (err || result.length == 0) {
                return;
            }
            let options = {
                hostname: config.wlyyServerConfig.host,
                port: config.wlyyServerConfig.port,
                path: '/wlyy/doctor/message/messages',
                method: 'POST',
                headers: {
                    'userAgent': '{"token":"' + result[0].token + '","uid":"' + userId + '","imei":"' + result[0].imei + '"}'
            WlyySDK.request(userId, '', result[0].token, result[0].imei, '/wlyy/doctor/message/messages', 'POST', function (err, res) {
                let count = 0;
                if (res.status == 200) {
                    let data = JSON.parse(res).data;
                    count = JSON.parse(data.imMsgCount).count + data.system.amount + data.healthIndex.amount + data.sign.amount;
                }
            };
            let req = http.request(options, function (res) {
                log.info('家庭医生平台->开始请求: http://', options.hostname + ":" + options.port + options.path);
                res.setEncoding('utf8');
                res.on('data', function (chunk) {
                    log.info('家庭医生平台->请求成功:', chunk);
                    let count = 0;
                    if (chunk.status == 200) {
                        let data = JSON.parse(chunk).data;
                        count = JSON.parse(data.imMsgCount).count + data.system.amount + data.healthIndex.amount + data.sign.amount;
                    }
                    response.status(200).send({"count": count});
                });
                response.status(200).send({"count": count});
            });
            req.on('error', function (e) {
                log.error('家庭医生平台->请求失败: ', e.message);
            });
            req.end();
        }
    });
});

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

@ -43,7 +43,6 @@ const TOPIC_STATUS = {
    ENDED: 10        // 结束
};
/**
 * 会话业务类型
 */

+ 7 - 7
src/server/models/client/wechat.client.js

@ -1,5 +1,5 @@
/**
 * 微信客户端。
 * 用户微信客户端。
 */
"use strict";
@ -53,7 +53,7 @@ class WechatClient extends RedisModel {
     */
    static sendMessage(message) {
        let patientClient = clientCache.findById(message.to);
        let self = this;
        if (patientClient) {
            WechatClient.sendViaWebSocket(patientClient.socket, message);
        } else {
@ -75,7 +75,7 @@ class WechatClient extends RedisModel {
     */
    static sendViaTemplateMessage(message) {
        function sendWxMessage(openid, name, topic) {
            var replyContent = message.content;
            let replyContent = message.content;
            switch (Number.parseInt(message.contentType)) {
                case CONTENT_TYPES.Image:
                    replyContent = "[图片]";
@ -116,7 +116,7 @@ class WechatClient extends RedisModel {
                return;
            }
            var openid = result && result.length > 0 ? result[0].openid : "";
            let openid = result && result.length > 0 ? result[0].openid : "";
            if (openid) {
                DoctorRepo.findOne(message.from, function (err, result) {
                    if (err) {
@ -125,8 +125,8 @@ class WechatClient extends RedisModel {
                    }
                    if (result && result.length > 0) {
                        var name = result[0].name;
                        var topic = result && result.length > 0 ? result[0] : "";
                        let name = result[0].name;
                        let topic = result && result.length > 0 ? result[0] : "";
                        if (topic) {
                            sendWxMessage(openid, name, topic);
                        }
@ -135,7 +135,7 @@ class WechatClient extends RedisModel {
                    }
                });
            } else {
                ModelUtil.logError("User does not bind with wechat, user id: " + message.to, err);
                ModelUtil.logError("User haven't bound with wechat, user id: " + message.to, err);
            }
        });
    };

+ 21 - 0
src/server/models/schedule/job.initializer.js

@ -0,0 +1,21 @@
'use strict';
let Scheduler = require('./scheduler');
let TopicTerminatingJob = require('./jobs/topic.terminating.job');
let configFile = require('../../include/commons').CONFIG_FILE;
let config = require('../../resources/config/' + configFile);
const TOPIC_TERMINATING_CRON = config.topicConfig.TERMINATING_CRON;
class JobInitializer {
    constructor() {
    }
    // schedule jobs
    static init() {
        Scheduler.cronSchedule(TOPIC_TERMINATING_CRON, TopicTerminatingJob.exec);
    }
}
module.exports = JobInitializer;

+ 45 - 0
src/server/models/schedule/jobs/topic.terminating.job.js

@ -0,0 +1,45 @@
/**
 * 议题终结任务。
 *
 * author: Sand
 * since: 2016/11/28
 */
"use strict";
let TopicRepo = require('../../../repository/mysql/topics.repo');
let ModelUtil = require("../../../util/model.util.js");
let WlyySDK = require("../../../util/wlyy.sdk");
let log = require("../../../util/log.js");
let configFile = require('../../../include/commons').CONFIG_FILE;
let config = require('../../../resources/config/' + configFile);
const TOPIC_TTL = config.topicConfig.TTL;
class TopicTerminatingJob {
    constructor() {
    }
    static exec() {
        TopicRepo.findAllBySessionLastActiveTime(TOPIC_TTL, function (err, topics) {
            if (err) {
                return ModelUtil.logError("Find topics without activity over " + TOPIC_TTL + " hour(s) failed", err);
            }
            topics.forEach(function (topic) {
                let topicId = topic.topic_id;
                let topicName = topic.topic_name;
                WlyySDK.request('admin', '0a5c5258-8863-4b07-a3f9-88c768528ab4', '', 'admin_imei', '/wlyy/doctor/consult/finish_consult?consult=' + topicId, 'GET', function (err, res) {
                    log.info("Got topic to terminate, " + topicId + ": " + topicName + ", last active time: " + topic.last_message_time);
                    if (err) return log.error("Terminating topic failed, try next time, error: ", err);
                    log.info("Terminating topic " + topicId + " succeed");
                });
            });
        });
    }
}
module.exports = TopicTerminatingJob;

+ 0 - 21
src/server/models/schedule/push.job.loader.js

@ -1,21 +0,0 @@
/**
 * 推送消息加载器。应用启动时使用此加载器将将来需要推送的消息加入消息调度中。
 *
 * author: Sand
 * since: 2016/11/28
 */
"use strict";
let Schedule = require('./schedule');
let log = require("../../util/log.js");
let doctorRepo = require('../../repository/mysql/doctor.repo');
class PushJobLoader{
    constructor(){}
    static load(){
    }
}
module.exports = PushJobLoader;

+ 2 - 2
src/server/models/schedule/schedule.js

@ -11,7 +11,7 @@
let nodeSchedule = require('node-schedule');
let log = require("../../util/log.js");
class Schedule {
class Scheduler {
    constructor(){
    }
@ -28,4 +28,4 @@ class Schedule {
    }
}
module.exports = Schedule;
module.exports = Scheduler;

+ 45 - 32
src/server/repository/mysql/topics.repo.js

@ -26,7 +26,7 @@ class TopicRepo {
            sql: sql,
            args: [topicId],
            handler: handler || function (err, res) {
                if(err) log.error(err);
                if (err) log.error(err);
            }
        });
    }
@ -53,7 +53,7 @@ class TopicRepo {
            sql: sql,
            args: [topicId],
            handler: handler || function (err, res) {
                if(err) log.error(err);
                if (err) log.error(err);
            }
        });
    }
@ -72,7 +72,7 @@ class TopicRepo {
            sql: sql,
            args: [sessionId],
            handler: handler || function (err, res) {
                if(err) log.error(err);
                if (err) log.error(err);
            }
        });
    }
@ -80,7 +80,7 @@ class TopicRepo {
    /**
     * 获取会话中的议题。
     *
     * @param sessionId
     * @param id
     * @param handler
     */
    static findAllByTopicId(id, handler) {
@ -91,27 +91,24 @@ class TopicRepo {
            sql: sql,
            args: [id],
            handler: handler || function (err, res) {
                if(err) log.error(err);
                if (err) log.error(err);
            }
        });
    }
    static findAllBySessionIdsAndStatus(sessionIds,status,page,pagesize,handler){
    static findAllBySessionIdsAndStatus(sessionIds, status, page, pagesize, handler) {
        let sql = "select id, session_id, name, create_time, end_by, end_time," +
            " start_message_id, end_message_id, description, status from " + DB_TABLES.Topics + " where session_id in ('"+sessionIds+"') and status in ("+status+") order by status desc limit ?,? ";
            " start_message_id, end_message_id, description, status from " + DB_TABLES.Topics + " where session_id in ('" + sessionIds + "') and status in (" + status + ") order by status desc limit ?,? ";
        ImDb.execQuery({
            sql: sql,
            args: [page,pagesize],
            args: [page, pagesize],
            handler: handler || function (err, res) {
                if(err) log.error(err);
                if (err) log.error(err);
            }
        });
    }
    /**
     * 保存议题
     *
@ -122,14 +119,15 @@ class TopicRepo {
     * @param date
     * @param description
     * @param status
     * @param handler
     */
    static saveTopic(topicName, topicId, sessionId, messageId, date, description, status,handler) {
    static saveTopic(topicName, topicId, sessionId, messageId, date, description, status, handler) {
        let sql = "insert into " + DB_TABLES.Topics + " (id,session_id,name,create_time,start_message_id,description,status) VALUES (?,?,?,?,?,?,?)";
        ImDb.execQuery({
            "sql": sql,
            "args": [topicId, sessionId, topicName, date, messageId, description, status],
            "handler": function (err, res) {
                handler(err,res);
                handler(err, res);
            }
        });
    }
@ -163,31 +161,46 @@ class TopicRepo {
     *
     * @param topicId
     * @param jsonValue
     * @param handler
     */
    static updateTopics(topicId, jsonValue,handler) {
        var values = [];
        let sql = "update topics set ";
        var key =[];
        for(var j in jsonValue){
            key.push(j+" = ?");
    static updateTopics(topicId, jsonValue, handler) {
        let values = [];
        let sql = "UPDATE topics SET ";
        let key = [];
        for (let j in jsonValue) {
            key.push(j + " = ?");
            values.push(jsonValue[j]);
        }
        sql = sql+key.join(",");
        sql = sql + " where  id = ?";
        sql = sql + key.join(",");
        sql = sql + " WHERE id = ?";
        values.push(topicId);
        ImDb.execQuery({
            "sql": sql,
            "args": values,
            "handler": function (err, res) {
                if (err) {
                    log.error("updateTopis is fail error: " + err);
                } else {
                    log.info("updateTopis is success");
                    if(handler){
                        handler(err,res);
                    }
                }
            }
            "handler": handler
        });
    }
    /**
     * 搜索最后回复时间超过指定时限的议题,此议题最后一条消息的回复者必须是医生,即医生发送消息后,患者未理睬的,关闭。
     *
     * @param timespan 时限,以小时计
     * @param handler
     */
    static findAllBySessionLastActiveTime(timespan, handler) {
        let sql = "SELECT s.id session_id, s.name session_name, s.create_date session_create_time, s.last_message_time, " +
            "t.id topic_id, t.name topic_name, t.create_time topic_create_time, t.start_message_id " +
            "FROM sessions s, topics t " +
            "WHERE s.id = t.session_id AND t.end_message_id IS NULL AND s.last_sender_id IN (SELECT id FROM doctors d)" +
            "AND UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(s.last_message_time) > ? " +
            "ORDER BY t.create_time";
        ImDb.execQuery({
            sql: sql,
            args: [timespan * 3600],
            handler: handler
        });
    }
}

+ 2 - 2
src/server/repository/mysql/wechat.token.repo.js

@ -6,8 +6,8 @@
 */
"use strict";
var log = require('../../util/log');
var ImDb = require('../mysql/db/im.db');
let log = require('../../util/log');
let ImDb = require('../mysql/db/im.db');
class WeChatTokenRepo {
    constructor() {

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

@ -59,6 +59,12 @@ let sessionConfig = {
    expireSessionCleanCount: 10              // 每次清理多少个过期会话
};
// 议题配置
let topicConfig = {
    TTL: 24,                            // 议题的存活时间,TTL = Time To Live
    TERMINATING_CRON: "2 * * * * *"     // 议题自动关闭的任务执行时间间隔
};
exports.app = 'IM.Server';
exports.version = '2.0.0';
exports.debug = true;
@ -73,3 +79,4 @@ exports.getTuiConfig = getTuiConfig;
exports.wlyyServerConfig = wlyyServerConfig;
exports.wechatConfig = wechatConfig;
exports.sessionConfig = sessionConfig;
exports.topicConfig = topicConfig;

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

@ -50,6 +50,12 @@ let sessionConfig = {
    expireSessionCleanCount: 10             // 每次清理多少个过期会话
};
// 议题配置
let topicConfig = {
    TTL: 24,                                // 议题的存活时间,TTL = Time To Live
    TERMINATING_CRON: "* 59 * * * *"        // 议题自动关闭的任务执行时间间隔
};
exports.app = 'im.server';
exports.version = '2.0.0';
exports.debug = true;
@ -63,4 +69,5 @@ exports.redisConfig = redisConfig;
exports.getTuiConfig = getTuiConfig;
exports.wlyyServerConfig = wlyyServerConfig;
exports.wechatConfig = wechatConfig;
exports.sessionConfig = sessionConfig;
exports.sessionConfig = sessionConfig;
exports.topicConfig = topicConfig;

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

@ -40,12 +40,21 @@ let wechatConfig = {
        consultTemplate: 'qSOW0DBxO3qEBm4ucG0Ial0jxsOyD7_f2TFK5e-mQEc'  // 咨询回复模板
    }
};
// 会话配置
let sessionConfig = {
    maxMessageCount: 1000,                  // 会话缓存的消息数量
    maxMessageTimespan: 7 * 24 * 3600,      // 会话缓存的最大时间跨度
    expireSessionCleanCount: 10             // 每次清理多少个过期会话
};
// 议题配置
let topicConfig = {
    TTL: 24,                                // 议题的存活时间,TTL = Time To Live
    TERMINATING_CRON: "* 30 * * * *"        // 议题自动关闭的任务执行时间间隔
};
exports.app = 'im.server';
exports.version = '2.0.0';
exports.debug = true;
@ -59,4 +68,5 @@ exports.redisConfig = redisConfig;
exports.getTuiConfig = getTuiConfig;
exports.wlyyServerConfig = wlyyServerConfig;
exports.wechatConfig = wechatConfig;
exports.sessionConfig = sessionConfig;
exports.sessionConfig = sessionConfig;
exports.topicConfig = topicConfig;

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

@ -7,10 +7,13 @@ create or replace view patients as
select code id, name, sex, birthday birthdate, photo avatar, openid
from wlyy.wlyy_patient;
CREATE OR REPLACE VIEW user_tokens AS
SELECT id, user, platform, timeout, imei, token
FROM wlyy.wlyy_token;
create or replace view wechat_status as
select code user_id, openid open_id
from wlyy.wlyy_patient
;
from wlyy.wlyy_patient;
/*三师后台咨询消息ID字段类型修改*/
ALTER TABLE wlyy.wlyy_consult_team

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

@ -29,7 +29,7 @@ class DbUtil {
            if (args) {
                connection.query(sql, args, function (err, results) {
                    if (err) {
                        log.error("Execute SQL failed: " + sql);
                        log.error("Execute SQL failed, arguments: " + args + ", sql: " + sql);
                        return handler(err, results);
                    }

+ 9 - 9
src/server/util/wechat.sdk.js

@ -27,8 +27,8 @@ class WechatSDK {
                return;
            }
            var data = result && result.length > 0 ? result[0] : null;
            var accessToken = "";
            let data = result && result.length > 0 ? result[0] : null;
            let accessToken = "";
            // 判断access_token是否有效
            if (data) {
@ -39,12 +39,12 @@ class WechatSDK {
            // access_token为空时从微信新获取并执行回调,否则直接执行回调
            if (!accessToken) {
                var basePath = "https://api.weixin.qq.com/cgi-bin/token?";
                var params = "grant_type=client_credential&appid=" + config.wechatConfig.appId
                let basePath = "https://api.weixin.qq.com/cgi-bin/token?";
                let params = "grant_type=client_credential&appid=" + config.wechatConfig.appId
                    + "&secret=" + config.wechatConfig.appSecret;
                https.get(basePath + params, function (res) {
                    var data = '';
                    let data = '';
                    res.on('data', (d) => {
                        data += d;
                    });
@ -86,8 +86,8 @@ class WechatSDK {
            }
            // 发送模板消息
            var messageJSON = JSON.stringify(message);
            var request = https.request({
            let messageJSON = JSON.stringify(message);
            let request = https.request({
                    host: 'api.weixin.qq.com',
                    path: '/cgi-bin/message/template/send?access_token=' + token,
                    method: 'POST'
@ -95,13 +95,13 @@ class WechatSDK {
                function (res) {
                    res.setEncoding('utf8');
                    var data = "";
                    let data = "";
                    res.on('data', (d) => {
                        data += d;
                    });
                    res.on('end', () => {
                        var result = JSON.parse(data);
                        let result = JSON.parse(data);
                        if (result && result.errcode === 0) {
                            log.info("send wechat template message success:" + messageJSON);

+ 51 - 0
src/server/util/wlyy.sdk.js

@ -0,0 +1,51 @@
/**
 * 简易WLYY SDK,提供WLYY接口调用。
 */
'use strict';
let http = require('http');
let configFile = require('../include/commons').CONFIG_FILE;
let config = require('../resources/config/' + configFile);
let log = require('./log.js');
class WlyySDK {
    constructor(){}
    static request(userId, adminToken, token, imei, endpoint, method, handler){
        let userAgent = {
            admin_token: adminToken,
            token: token,
            uid: userId,
            imei: imei
        };
        let options = {
            hostname: config.wlyyServerConfig.host,
            port: config.wlyyServerConfig.port,
            path: endpoint,
            method: method,
            headers: {
                'userAgent': JSON.stringify(userAgent)
            }
        };
        let req = http.request(options, function (res) {
            res.setEncoding('utf8');
            res.on('data', function (chunk) {
                log.info('家庭医生平台->请求成功:', chunk);
                handler(null, res);
            });
        });
        req.on('error', function (err) {
            log.error('家庭医生平台->请求失败: ', err.message);
            handler(err, null);
        });
        req.end();
    }
}
module.exports = WlyySDK;

+ 2 - 2
test/client/im.client.application.Test.js

@ -13,7 +13,7 @@ let imClient = require('../../src/client/im.client');
// 测试会话用的数据, test data
let TD = {
    DoctorA: {
        id: "0de7295862dd11e69faffa163e8aee56"
        id: "D2016008240003"
    }
};
@ -23,7 +23,7 @@ describe("Application API", function () {
        it("return success when with valid user", function (done) {
            imClient.Application.getBadgeNo(TD.DoctorA.id,
            function (data) {
                assert.ok(Object.keys(data).length > 0, "Do you forget finish the API?");
                assert.ok(data.count >= 0, "Do you forget finish the API?");
                done();
            },
            function (xhr, error, status) {