From 7bba12b6ac1e4753cbd0d5589fd5492d3af5cec3 Mon Sep 17 00:00:00 2001 From: "vincent.wen" Date: Thu, 5 Nov 2015 22:59:47 +0800 Subject: [PATCH 1/6] support dynamic list --- README.md | 21 +++ lib/list.js | 98 +++++++++- lib/wechat.js | 82 ++++++--- package.json | 5 +- test/dynamic_list.test.js | 368 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 544 insertions(+), 30 deletions(-) create mode 100644 test/dynamic_list.test.js diff --git a/README.md b/README.md index ebc67c5b..553a5f44 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,27 @@ List.add('view', [ 如果用户触发等待回复事务后,没有按照`{}`中的进行回复,那么将会由原有的默认函数进行处理。在原有函数中,可以选择调用`res.nowait()`中断事务。`nowait()`除了能中断事务外,与`reply`的行为一致。 +### 动态List +单进程情况下,直接重新List.add()即可。多进程模式下,使用`serializeList(fn)`及`deserializeList(fn)`注册序列化和反序列化方法,将list保存在数据库或文件等外部,使进程间可以共享。需要在服务启动前注册相应序列化和反序列化方法。 + +**注意:serializeList接受的方法中的参数list,其实已经进行过序列化,是string对象,可以直接保存,这里名字用serializeList可能不太妥。相应的List.deserializeList中的方法参数list,只要对应serializeList中一致的string对象即可** + +``` +List.serializeList(function(name, list, done) { + store[name] = list; + return done && done(null); + }); + + List.deserializeList(function(name, done) { + var list = store[name]; + done(null, list); + }); +``` +当serializer和deserializer都注册之后,List的状态会自动变为dynamic,否则仍然为静态。重置动态List,移除serializer和deserializer使用`List.reset()` + +####动态列表失效问题 +当用户收到列表,发送选项这段时间之间,列表已经被修改,将返回列表过期提示,请重回重新获取列表。默认提示:“列表已过期,请重新获取列表”。修改提示`List.setInvalidListTips(tips)` + ## Show cases ### Node.js API自动回复 diff --git a/lib/list.js b/lib/list.js index a1508ce2..0c2ae958 100644 --- a/lib/list.js +++ b/lib/list.js @@ -1,4 +1,5 @@ var util = require('util'); +var serialize = require('node-serialize'); /*! * 缓存列表 */ @@ -11,6 +12,14 @@ var List = function () { this.map = {}; }; + +var serializer = null; +var deserializer = null; +var DEFAULT_INVALID_LIST_TIPS = "列表已过期,请重新获取列表"; +var invalidListTips = DEFAULT_INVALID_LIST_TIPS; +var dynamic = false; + + /** * 从List对象中根据key取出对应的handler * @param {String} key 列表中的关键词 @@ -19,6 +28,8 @@ List.prototype.get = function (key) { return this.map[key]; }; + + /** * 静态方法,根据items生成List对象,并放置到缓存中 * @param {String} name 列表名字 @@ -27,7 +38,7 @@ List.prototype.get = function (key) { * @param {String} delimiter 回复分隔符 * @param {String} foot 回复底部 */ -List.add = function (name, items, head, delimiter, foot) { +List.add = function (name, items, head, delimiter, foot, done) { var description = []; var list = new List(); list.name = name; @@ -37,25 +48,102 @@ List.add = function (name, items, head, delimiter, foot) { var replaced = text.replace(/\{(.*)\}/, function (match, key) { list.map[key] = item[1]; return key; - }); + }); description.push(replaced); }); + if (delimiter) { var lists = description.join('\n' + delimiter + '\n'); list.description = util.format('%s\n%s\n%s', head || '', lists, (foot || '')); } else { list.description = description.join('\n'); } - listCache[name] = list; + + if(dynamic) { + list.createdAt = new Date().getTime(); + var serializedList = serialize.serialize(list); + serializer(list.name, serializedList, done); + } else { + listCache[name] = list; + } }; +/** + * 设置序列化方法,保存动态List + * @param {Function} fn 序列化方法 + */ +List.serializeList = function(fn) { + serializer = fn; + if(serializer && deserializer) { + dynamic = true; + } +} +/** + * 设置反序列化方法,获取动态List + * @param {Function} fn 反序列化方法 + * @return {void} + */ +List.deserializeList = function(fn) { + deserializer = fn; + if(serializer && deserializer) { + dynamic = true; + } +} + +/** + * 静态方法,判断当前List是不是动态 + * @return {Boolean} + */ +List.isDynamic = function() { + return dynamic; +} + +/** + * 静态方法,获取列表过期提示 + * @return {string} 当前列表过期提示内容 + */ +List.getInvalidListTips = function() { + return invalidListTips; +} + +/** + * 静态方法,设置列表过期提示 + * @param {string} tips 列表过期提示内容 + */ +List.setInvalidListTips = function(tips) { + invalidListTips = tips; +} +/** + * 静态方法,重置List + */ +List.reset = function() { + serializer = null; + deserializer = null; + dynamic = false; + invalidListTips = DEFAULT_INVALID_LIST_TIPS; +} + /** * 静态方法,从缓存中根据名字取出List对象 * @param {String} name 列表名字 */ -List.get = function (name) { - return listCache[name]; +List.get = function (name, done) { + if(dynamic) { + deserializer(name, function(err,serializedList){ + if(err) return done(err); + var list = serialize.unserialize(serializedList); + var originList = new List(); + originList.map = list.map; + originList.name = list.name; + originList.description = list.description; + originList.createdAt = list.createdAt; + done(null, originList); + }); + } else { + //still return the list immediately when use static + return listCache[name]; + } }; /** diff --git a/lib/wechat.js b/lib/wechat.js index a6c428ee..dd8ea067 100644 --- a/lib/wechat.js +++ b/lib/wechat.js @@ -276,29 +276,49 @@ var respond = function (handler) { }; var done = function () { - // 如果session中有_wait标记 - if (message.MsgType === 'text' && req.wxsession && req.wxsession._wait) { - var list = List.get(req.wxsession._wait); - var handle = list.get(message.Content); - var wrapper = function (message) { + var callNext = function(handle) { + var wrapper = function(message) { return handler.handle ? function(req, res) { res.reply(message); - } : function (info, req, res) { + } : function(info, req, res) { res.reply(message); }; }; - // 如果回复命中规则,则用预置的方法回复 if (handle) { callback = typeof handle === 'string' ? wrapper(handle) : handle; } + // 兼容旧API + if (handler.handle) { + callback(req, res, next); + } else { + callback(message, req, res, next); + } } - // 兼容旧API - if (handler.handle) { - callback(req, res, next); + // 如果session中有_wait标记 + if (message.MsgType === 'text' && req.wxsession && req.wxsession._wait) { + var handle = null; + if (List.isDynamic()) { + List.get(req.wxsession._wait, function(err, list) { + var time = req.wxsession._sentAt; + var listSentAt = time; + if (!list || list.createdAt > listSentAt) { + handle = List.getInvalidListTips(); + //列表过期,清楚wait + delete req.wxsession._wait; + } else { + handle = list.get(message.Content); + } + callNext(handle); + }); + } else { + var list = List.get(req.wxsession._wait); + handle = list.get(message.Content); + callNext(handle); + } } else { - callback(message, req, res, next); + callNext(); } }; @@ -306,30 +326,46 @@ var respond = function (handler) { var storage = req.sessionStore; var _end = res.end; var openid = message.FromUserName + ':' + message.ToUserName; - res.end = function () { + res.end = function() { _end.apply(res, arguments); if (req.wxsession) { req.wxsession.save(); } }; // 等待列表 - res.wait = function (name, callback) { - var list = List.get(name); - if (list) { - req.wxsession._wait = name; - res.reply(list.description); + res.wait = function(name, callback) { + var callNext = function(err, list, dynamic) { + if (!err && list) { + req.wxsession._wait = name; + if (dynamic) { + req.wxsession._sentAt = new Date().getTime(); + } + res.reply(list.description); + } else { + err = err ? new Error(err) : new Error('Undefined list: ' + name); + err.name = 'UndefinedListError'; + res.writeHead(500); + res.end(err.name); + callback && callback(err); + } + } + if (List.isDynamic()) { + List.get(name, function(err, list) { + callNext(err, list, true); + }); } else { - var err = new Error('Undefined list: ' + name); - err.name = 'UndefinedListError'; - res.writeHead(500); - res.end(err.name); - callback && callback(err); + var list = List.get(name); + callNext(null, list); } + }; // 清除等待列表 - res.nowait = function () { + res.nowait = function() { delete req.wxsession._wait; + if (List.isDynamic()) { + delete req.wxsession._sentAt; + } res.reply.apply(res, arguments); }; diff --git a/package.json b/package.json index ac547782..fce6e7b4 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,10 @@ "wechat" ], "dependencies": { - "xml2js": "0.4.15", "ejs": ">=1.0.0", - "wechat-crypto": "0.0.2" + "node-serialize": "0.0.4", + "wechat-crypto": "0.0.2", + "xml2js": "0.4.15" }, "devDependencies": { "supertest": "*", diff --git a/test/dynamic_list.test.js b/test/dynamic_list.test.js new file mode 100644 index 00000000..82ccc05c --- /dev/null +++ b/test/dynamic_list.test.js @@ -0,0 +1,368 @@ +require('should'); +var List = require('../').List; + +var request = require('supertest'); +var template = require('./support').template; +var tail = require('./support').tail; + +var connect = require('connect'); +var wechat = require('../'); + + +beforeEach(function() { + List.reset(); +}); + + +function initDynamicList() { + var store = {}; + List.serializeList(function(name, list, done) { + store[name] = list; + return done && done(null); + }); + + List.deserializeList(function(name, done) { + var list = store[name]; + done(null, list); + }); +}; + +function sendRequest(info, cb) { + request(app) + .post('/wechat' + tail()) + .send(template(info)) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + cb && cb(null, res); + }); +} + + +var app = connect(); +app.use(connect.query()); +app.use(connect.cookieParser()); +app.use(connect.session({ + secret: 'keyboard cat', + cookie: { + maxAge: 60000 + } +})); +app.use('/wechat', wechat('some token', wechat.text(function(info, req, res, next) { + // 微信输入信息都在req.weixin上 + var info = req.weixin; + if (info.Content === 'list') { + res.wait('view'); + } else { + res.reply("呵呵"); + } +}))); + + +describe('list', function() { + + it('should ok with dynamic list', function(done) { + initDynamicList(); + List.add('view', [ + ['选择{a}查看啥', function(message, req, res) { + res.nowait("this is answer a."); + }], + ['选择{b}查看啥', function() {}], + ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] + ]); + var listInfo = { + sp: 'test', + user: 'dynamic1', + type: 'text', + text: 'list' + }; + + var answerInfo = { + sp: 'test', + user: 'dynamic1', + type: 'text', + text: 'c' + }; + + sendRequest(listInfo, function(err, res) { + nextStep(); + }); + var nextStep = function() { + sendRequest(answerInfo, function(err, res) { + var body = res.text.toString(); + body.should.include('这样的事情怎么好意思告诉你啦'); + done(); + }); + } + }); + + + + it('should okay with updated List', function(done) { + initDynamicList(); + var listInfo = { + sp: 'test', + user: 'dynamic2', + type: 'text', + text: 'list' + }; + + var answerInfo = { + sp: 'test', + user: 'dynamic2', + type: 'text', + text: 'a' + }; + + List.add('view', [ + ['选择{a}查看啥', function(message, req, res) { + res.nowait("this is answer a."); + }], + ['选择{b}查看啥', function() {}], + ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] + ], "header", "...", "foot", function() { + sendRequest(listInfo, function(err, res) { + nextStep(); + }); + }); + + var nextStep = function() { + sendRequest(answerInfo, function(err, res) { + var body = res.text.toString(); + body.should.include('this is answer a.'); + retryList(); + }); + } + + function retryList() { + List.add('view', [ + ['选择{a}查看啥', function(message, req, res) { + res.end("this is updated answer a."); + }], + ['选择{b}查看啥', function() {}], + ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] + ], "header", "...", "foot", function() { + sendRequest(listInfo, function(err, res) { + retryAnswer(); + }); + }); + } + + function retryAnswer() { + sendRequest(answerInfo, function(err, res) { + var body = res.text.toString(); + body.should.include('this is updated answer a.'); + done(); + }); + } + }); + + + it('should get invalid list message when list is outdated ', function(done) { + initDynamicList(); + var listInfo = { + sp: 'test', + user: 'dynamic3', + type: 'text', + text: 'list' + }; + + var answerInfo = { + sp: 'test', + user: 'dynamic3', + type: 'text', + text: 'a' + }; + + List.add('view', [ + ['选择{a}查看啥', function(message, req, res) { + res.nowait("this is answer a."); + }], + ['选择{b}查看啥', function() {}], + ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] + ], "header", "...", "foot", function() { + sendRequest(listInfo, function(err, res) { + nextStep(); + }); + }); + + function nextStep() { + List.add('view', [ + ['选择{a}查看啥', function(message, req, res) { + res.end("this is updated answer a."); + }], + ['选择{b}查看啥', function() {}], + ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] + ], "header", "...", "foot", function() { + sendRequest(answerInfo, function(err, res) { + var body = res.text.toString(); + body.should.include('列表已过期,请重新获取列表'); + done(); + }); + }); + } + }); + + it('should get custom invalid list message when list is outdated ', function(done) { + initDynamicList(); + List.setInvalidListTips("New Tips."); + var listInfo = { + sp: 'test', + user: 'dynamic4', + type: 'text', + text: 'list' + }; + + var answerInfo = { + sp: 'test', + user: 'dynamic4', + type: 'text', + text: 'a' + }; + + List.add('view', [ + ['选择{a}查看啥', function(message, req, res) { + res.nowait("this is answer a."); + }], + ['选择{b}查看啥', function() {}], + ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] + ], "header", "...", "foot", function() { + sendRequest(listInfo, function(err, res) { + nextStep(); + }); + }); + + function nextStep() { + List.add('view', [ + ['选择{a}查看啥', function(message, req, res) { + res.end("this is updated answer a."); + }], + ['选择{b}查看啥', function() {}], + ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] + ], "header", "...", "foot", function() { + sendRequest(answerInfo, function(err, res) { + var body = res.text.toString(); + body.should.include('New Tips.'); + done(); + }); + }); + } + }); + + it('should accept user retry after invalid list', function(done) { + initDynamicList(); + var listInfo = { + sp: 'test', + user: 'dynamic5', + type: 'text', + text: 'list' + }; + + var answerInfo = { + sp: 'test', + user: 'dynamic5', + type: 'text', + text: 'a' + }; + + List.add('view', [ + ['选择{a}查看啥', function(message, req, res) { + res.nowait("this is answer a."); + }], + ['选择{b}查看啥', function() {}], + ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] + ], "header", "...", "foot", function() { + sendRequest(listInfo, function(err, res) { + nextStep(); + }); + }); + + function nextStep() { + List.add('view', [ + ['选择{a}查看啥', function(message, req, res) { + res.end("this is updated answer a."); + }], + ['选择{b}查看啥', function() {}], + ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] + ], "header", "...", "foot", function() { + sendRequest(answerInfo, function(err, res) { + var body = res.text.toString(); + body.should.include('列表已过期,请重新获取列表'); + retryList(); + }); + }); + } + + function retryList() { + sendRequest(listInfo, function(err, res) { + retryAnswser(); + }); + } + + function retryAnswser() { + sendRequest(answerInfo, function(err, res) { + var body = res.text.toString(); + body.should.include('this is updated answer a.'); + done(); + }); + } + }); + + it('should clear status after invalid list', function(done) { + initDynamicList(); + var listInfo = { + sp: 'test', + user: 'dynamic6', + type: 'text', + text: 'list' + }; + + var answerInfo = { + sp: 'test', + user: 'dynamic6', + type: 'text', + text: 'a' + }; + + List.add('view', [ + ['选择{a}查看啥', function(message, req, res) { + res.nowait("this is answer a."); + }], + ['选择{b}查看啥', function() {}], + ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] + ], "header", "...", "foot", function() { + sendRequest(listInfo, function(err, res) { + nextStep(); + }); + }); + + function nextStep() { + List.add('view', [ + ['选择{a}查看啥', function(message, req, res) { + res.end("this is updated answer a."); + }], + ['选择{b}查看啥', function() {}], + ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] + ], "header", "...", "foot", function() { + sendRequest(answerInfo, function(err, res) { + var body = res.text.toString(); + body.should.include('列表已过期,请重新获取列表'); + retryAnswser(); + }); + }); + } + + + function retryAnswser() { + sendRequest(answerInfo, function(err, res) { + var body = res.text.toString(); + body.should.include('呵呵'); + done(); + }); + } + }); + + +}); \ No newline at end of file From 65237177a3f62c8b36de2d091b5b0478eb57bf75 Mon Sep 17 00:00:00 2001 From: "vincent.wen" Date: Fri, 6 Nov 2015 13:56:47 +0800 Subject: [PATCH 2/6] update tests, delay the netx request --- test/dynamic_list.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/dynamic_list.test.js b/test/dynamic_list.test.js index 82ccc05c..605140f4 100644 --- a/test/dynamic_list.test.js +++ b/test/dynamic_list.test.js @@ -132,7 +132,7 @@ describe('list', function() { sendRequest(answerInfo, function(err, res) { var body = res.text.toString(); body.should.include('this is answer a.'); - retryList(); + setTimeout(retryList, 10); }); } @@ -160,7 +160,7 @@ describe('list', function() { }); - it('should get invalid list message when list is outdated ', function(done) { + it('should get invalid list message when list is outdated', function(done) { initDynamicList(); var listInfo = { sp: 'test', @@ -184,7 +184,7 @@ describe('list', function() { ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] ], "header", "...", "foot", function() { sendRequest(listInfo, function(err, res) { - nextStep(); + setTimeout(nextStep, 10); }); }); @@ -230,7 +230,7 @@ describe('list', function() { ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] ], "header", "...", "foot", function() { sendRequest(listInfo, function(err, res) { - nextStep(); + setTimeout(nextStep, 10); }); }); @@ -275,7 +275,7 @@ describe('list', function() { ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] ], "header", "...", "foot", function() { sendRequest(listInfo, function(err, res) { - nextStep(); + setTimeout(nextStep, 10); }); }); @@ -334,7 +334,7 @@ describe('list', function() { ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] ], "header", "...", "foot", function() { sendRequest(listInfo, function(err, res) { - nextStep(); + setTimeout(nextStep, 10); }); }); From d5486b10fe4eea6f0d81c7efa4d5f6db69517c6f Mon Sep 17 00:00:00 2001 From: "vincent.wen" Date: Sat, 7 Nov 2015 16:30:51 +0800 Subject: [PATCH 3/6] allow make handle as object, so it can save the data --- lib/wechat.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/wechat.js b/lib/wechat.js index dd8ea067..34a14e59 100644 --- a/lib/wechat.js +++ b/lib/wechat.js @@ -305,10 +305,14 @@ var respond = function (handler) { var listSentAt = time; if (!list || list.createdAt > listSentAt) { handle = List.getInvalidListTips(); - //列表过期,清楚wait + //列表过期,清除wait delete req.wxsession._wait; } else { handle = list.get(message.Content); + if(typeof handle === "object") { + handle.action = handle.action.bind(handle); + handle = handle.action; + } } callNext(handle); }); From f20fab794a0de24a8689a8544f37b32c2d74d2fa Mon Sep 17 00:00:00 2001 From: "vincent.wen" Date: Sat, 7 Nov 2015 17:23:20 +0800 Subject: [PATCH 4/6] simplify arguments of List.add() --- lib/list.js | 10 ++++++++++ test/dynamic_list.test.js | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/list.js b/lib/list.js index 0c2ae958..d6b2b322 100644 --- a/lib/list.js +++ b/lib/list.js @@ -39,6 +39,16 @@ List.prototype.get = function (key) { * @param {String} foot 回复底部 */ List.add = function (name, items, head, delimiter, foot, done) { + if(typeof head === "function") { + done = head + } + if(typeof head === "object") { + var opts = head; + done = delimiter; + head = opts.head; + delimiter = opts.delimiter; + foot = opts.foot; + } var description = []; var list = new List(); list.name = name; diff --git a/test/dynamic_list.test.js b/test/dynamic_list.test.js index 605140f4..ca977da8 100644 --- a/test/dynamic_list.test.js +++ b/test/dynamic_list.test.js @@ -182,7 +182,7 @@ describe('list', function() { }], ['选择{b}查看啥', function() {}], ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] - ], "header", "...", "foot", function() { + ], function() { sendRequest(listInfo, function(err, res) { setTimeout(nextStep, 10); }); @@ -228,7 +228,7 @@ describe('list', function() { }], ['选择{b}查看啥', function() {}], ['回复{c}查看我的性取向', '这样的事情怎么好意思告诉你啦- -'] - ], "header", "...", "foot", function() { + ], { head:"header",delimiter:",",foot:"footer"}, function() { sendRequest(listInfo, function(err, res) { setTimeout(nextStep, 10); }); From 989a089353e179e05342b837b0deea0c8a020c7c Mon Sep 17 00:00:00 2001 From: "vincent.wen" Date: Sat, 7 Nov 2015 19:10:58 +0800 Subject: [PATCH 5/6] add new test for object handle --- test/dynamic_list.test.js | 45 +++++++++++++++++++++++++++++++++++++++ test/test-lib.js | 6 ++++++ 2 files changed, 51 insertions(+) create mode 100644 test/test-lib.js diff --git a/test/dynamic_list.test.js b/test/dynamic_list.test.js index ca977da8..7244a6f6 100644 --- a/test/dynamic_list.test.js +++ b/test/dynamic_list.test.js @@ -365,4 +365,49 @@ describe('list', function() { }); +it('should ok with object handle', function(done) { + initDynamicList(); + + var handle = {}; + handle.action = function(message,req, res) { + var format = require(process.cwd() + "/test/test-lib.js"); + res.reply(format(this.name) + ',这样的事情怎么好意思告诉你啦- -'); + } + handle.name = 'king'; + List.add('view', [ + ['选择{a}查看啥', function(message, req, res) { + res.nowait("this is answer a."); + }], + ['选择{b}查看啥', function() {}], + ['回复{c}查看我的性取向', handle] + ],function(){ + //do something after list added + }); + var listInfo = { + sp: 'test', + user: 'dynamic7', + type: 'text', + text: 'list' + }; + + var answerInfo = { + sp: 'test', + user: 'dynamic7', + type: 'text', + text: 'c' + }; + + sendRequest(listInfo, function(err, res) { + nextStep(); + }); + var nextStep = function() { + sendRequest(answerInfo, function(err, res) { + var body = res.text.toString(); + body.should.include('King,这样的事情怎么好意思告诉你啦'); + done(); + }); + } + }); + + }); \ No newline at end of file diff --git a/test/test-lib.js b/test/test-lib.js new file mode 100644 index 00000000..6f61b869 --- /dev/null +++ b/test/test-lib.js @@ -0,0 +1,6 @@ +module.exports = function(name) { + if(!name || typeof name !== "string") return ""; + var formattedName = name.toLowerCase().split(""); + formattedName[0] = formattedName[0].toUpperCase(); + return formattedName.join(""); +} \ No newline at end of file From f361af882f708cf9d8c7b4ba2ecce228d140eefb Mon Sep 17 00:00:00 2001 From: "vincent.wen" Date: Sat, 7 Nov 2015 19:11:33 +0800 Subject: [PATCH 6/6] update README --- README.en.md | 36 ++++++++++++++++++++++++++++++++++++ README.md | 16 +++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/README.en.md b/README.en.md index dd29b09f..4d701052 100644 --- a/README.en.md +++ b/README.en.md @@ -250,6 +250,42 @@ List.add('view', [ if user's message is not in waiter's trigger texts. this message will be processd in the `else` way and can be stoped by `res.nowait()`, `res.nowait` method actions like `reply` method. +### Dynamic List +In single process condition, just use `List.add()` to override the existing list.In multi-process condition,use `serializeList(fn)`and `deserializeList(fn)` to register serializer and deserializer functions,to save the list in the external storage, database, file etc. Serializer and deserializer should be registered before service starts.Since handle will be serialized, the context will chanage,so you can not refer to external variables in the handle . unless you have no references of external variable, you can use function as handle directly.if you need to pass any data to the function, need to make handle as an object, and has an `action` function which will accept arguments `(message, req, res,next)`, then set the variables into the object, and access those in the funciton via `this`, meanwhile you need use absolute path for 'require()', see below: + +``` +var handle = {}; +handle.action = function(message,req, res) { + var format = require(process.cwd() + "/test/test-lib.js"); + res.reply(format(this.name) + ',this is answerc'); +} +handle.name = 'king'; +List.add('view', [ + ['reply{c} to see the anwser', handle] +],function(){ + //do something after list added +})); +``` + +**Notice:the arugment `list` which passed to the serializer has already been serialized for simplicity,it's actullay a string object. The deserialize correspondingly need the same string object for the list** + +``` +List.serializeList(function(name, list, done) { + store[name] = list; + return done && done(null); + }); + + List.deserializeList(function(name, done) { + var list = store[name]; + done(null, list); + }); +``` +When serializer and deserializer are both registered, the List will change to dynamic automatically, or it will still be static.Reset the dynamic List, remove serializer and deserializer use `List.reset()` + + +####Invalid List (for dynamic) +Between receive list and send the option, if the list has been updated, it will reply the invalid list tips to tell the user request the new list again.Default tips in Chinese:"列表已过期,请重新获取列表".set custom tips `List.setInvalidListTips(tips)` + ## Show cases ### Auto-reply robot based on Node.js diff --git a/README.md b/README.md index 553a5f44..1761052c 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,21 @@ List.add('view', [ 如果用户触发等待回复事务后,没有按照`{}`中的进行回复,那么将会由原有的默认函数进行处理。在原有函数中,可以选择调用`res.nowait()`中断事务。`nowait()`除了能中断事务外,与`reply`的行为一致。 ### 动态List -单进程情况下,直接重新List.add()即可。多进程模式下,使用`serializeList(fn)`及`deserializeList(fn)`注册序列化和反序列化方法,将list保存在数据库或文件等外部,使进程间可以共享。需要在服务启动前注册相应序列化和反序列化方法。 +单进程情况下,直接重新`List.add()`即可。多进程模式下,使用`serializeList(fn)`及`deserializeList(fn)`注册序列化和反序列化方法,将list保存在数据库或文件等外部,使进程间可以共享。需要在服务启动前注册相应序列化和反序列化方法。由于handle会被序列化,context会被改变,所以无法直接在函数内部直接引用外部变量,除非是没有任何方法外引用,可以直接使用函数作为handle。如果需要接受外部参数,需要使用对象作为handle,并使用action函数作为handle,同时在`require()`时需要使用绝对路径,如下: + +``` +var handle = {}; +handle.action = function(message,req, res) { + var format = require(process.cwd() + "/test/test-lib.js"); + res.reply(format(this.name) + ',这样的事情怎么好意思告诉你啦- -'); +} +handle.name = 'king'; +List.add('view', [ + ['回复{c}查看我的性取向', handle] +],function(){ + //do something after list added +})); +``` **注意:serializeList接受的方法中的参数list,其实已经进行过序列化,是string对象,可以直接保存,这里名字用serializeList可能不太妥。相应的List.deserializeList中的方法参数list,只要对应serializeList中一致的string对象即可**