Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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自动回复

Expand Down
98 changes: 93 additions & 5 deletions lib/list.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var util = require('util');
var serialize = require('node-serialize');
/*!
* 缓存列表
*/
Expand All @@ -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 列表中的关键词
Expand All @@ -19,6 +28,8 @@ List.prototype.get = function (key) {
return this.map[key];
};



/**
* 静态方法,根据items生成List对象,并放置到缓存中
* @param {String} name 列表名字
Expand All @@ -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;
Expand All @@ -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];
}
};

/**
Expand Down
82 changes: 59 additions & 23 deletions lib/wechat.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,60 +276,96 @@ 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();
}
};

if (req.sessionStore) {
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);
};

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "*",
Expand Down
Loading