2.4.1 View部分
为了支持脚本系统,下面这些View,都没有定义明确的Control。而是通过Message方式实现,直接向特定的View发送自定义的字符串命令实现控制功能。
-
TemporaryView
-
ChatRoom的创建与进入
UserInfo.java:
private void joinRoom(long roomid, long sessionid) { Procedure.execute(ProcedureHelper.nameProcedure("joinRoom", () -> { ChatRoom view = table.Roominfocache.update(roomid); if (null == view) { ......................................... view = ChatRoom.createInstance(); view.setInfo(new ViewChatRoomInfo(info.getName(), roomid)); view.setRoomId(roomid); table.Roominfocache.insert(roomid, view); } view.getMembership().add(sessionid); return true; })); }
ChatRoom被保存在一个roomid索引的Cache中,如果Cache中没有则被创建出来,加入Cache。随后当前用户作为ChatRoom成员加入。
用户加入ChatRoom:
ChatRoom.java:
protected void onAttached(long sessionid) { RoomInfo.increment_memberCount_mapkey(roomid, 1); }
这里,服务器作了一个简单计数。在成员加入信息通告给所有成员之后,服务器生成这个消息告知服务器应用加入动作已经完成。
chatroom.js:
v100.chatviews.ChatRoom.onopen = function(instanceid, memberids) { currentChatView = this[instanceid]; showChatFrame(currentChatView); ctx.register(currentChatView, "info", function(e) { showChatRoomName(e.value); }); ctx.register(currentChatView, "names", function(e) { updateName(e.sessionid, e.value.nickname); }); ctx.register(currentChatView, "lastmessage", function(e) { if (e.type == 1 || e.type == 2) showMessageToAll(e.value.user, e.value.msg); }); }
新成员自身,当前的chatView在客户端创建出来,showChatFrame显示出聊天界面,注册了ChatRoom的3个字段的listener,监听之后的ChatRoom的信息变动。
v100.chatviews.ChatRoom.onattach = function(instanceid, memberid) { onMemberAttach(memberid); }
之前的成员,得到通告,有新成员加入了。随后,新成员的被订阅信息被发给之前的成员。
function onMemberAttach(sessionid) { var done = false; if (typeof (currentChatView[sessionid]) != "undefined" && typeof (currentChatView[sessionid].names) != "undefined") { showTextToMessages("[user \"" + currentChatView[sessionid].names.nickname + "\" enter room]"); done = true; } if (!done) setTimeout("onMemberAttach( " + sessionid + ")", 1); }
这里使用了一个1ms定时器探测收到的新成员被订阅信息(用户名),收到以后在聊天室报告加入消息。事实上,新成员进入消息与被订阅信息的数据打包送往客户端,只不过先于进入消息之前通告订阅信息逻辑上讲不合理,所以才会有这样的实现。存在避免使用定时器的方案——onMemberAttach使用容器记录新成员sessionid, 处理订阅信息时, 检查sessionid是否被记录,如果是,作为新成员加入处理,容器内删除sessionid,如果不是,按一般逻辑处理。
-
ChatRoom的离开
-
主动离开
用户向ChatRoom发送离开Message
chatroom.js:
function doLeave() { context.send(currentChatView, "leave="); }
ChatRoom.java:
protected void onMessage(String message, long sessionid) { int index = message.indexOf('='); if (-1 == index) return; final String cmd = message.substring(0, index).trim(); final String params = message.substring(index + 1); switch (cmd) { ............................... case "leave": { getMembership().remove(sessionid, (byte) 1); return; } } }
服务器从成员列表中移除当前用户,设置用户定义closeReason为1
ChatRoom.java:
protected void onDetached(long sessionid, byte reason) { RoomInfo.increment_memberCount_mapkey(roomid, -1L); ........................ }
这里,服务器作了一个简单计数。在成员离开信息通告给其余成员之后,服务器生成这个消息告知服务器应用离开动作已经完成。
离开用户自身
chatroom.js:
v100.chatviews.ChatRoom.onclose = function(instanceid) { showMainFrame(); currentChatView = null; }
showMainFrame,进入房间选择。
其它用户
chatroom.js:
v100.chatviews.ChatRoom.ondetach = function(instanceid, memberid, reason) { onMemberDetach(memberid, reason >= 0); }
通过onMemberDetach,报告用户离开信息,(reason==1)>= 0,报告memberid离开聊天室。
-
断线离开
ChatRoom.java:
protected void onDetached(long sessionid, byte reason) { RoomInfo.increment_memberCount_mapkey(roomid, -1L); ........................ }
这里,服务器作了一个简单计数。在成员离开信息通告给其余成员之后,服务器生成这个消息告知服务器应用离开动作已经完成。这里reason == -1
其它用户
chatroom.js:
v100.chatviews.ChatRoom.ondetach = function(instanceid, memberid, reason) { onMemberDetach(memberid, reason >= 0); }
这里reason & 0,onMemberDetach报告用户memberid断线。
-
-
ChatRoom的销毁
Main.java:
private static void updateViewHallsFromCache(long sessionid, String message, List
destroylist) { ............................................ ProcedureHelper.executeWhileCommit(() -> Engine .getApplicationExecutor().execute(() -> { SessionManager.setViewHallsFromCache(); destroylist.forEach(room -> room.destroyInstance()); })); } cmdmap.put("deleteroom", (sessionid, params) -> { if (params.length < 3 || !UserInfo.getInstance(sessionid).isCommandMode()) return; Procedure.execute(() -> { ............................................ ChatRoom view = table.Roominfocache.update(removeroomid); List<ChatRoom> roomlist = new ArrayList<>(); if (null != view) { roomlist.add(view); table.Roominfocache.delete(removeroomid); } updateViewHallsFromCache(sessionid, "delete room succeed",roomlist); return true; }); }); Roominfocache中删除room之后通过roomlist保存需要销毁的ChatRoom列表, updateViewHallsFromCache的执行以后, 调度一个事务提交后执行的任务, 该任务再次调度一个执行序列,首先通过SessionManager.setViewHallsFromCache();更新所有聊天室信息,接下来逐一删除保存的ChatRoom,之所以这样做是因为:
ChatRoom.java:
protected void onDetached(long sessionid, byte reason) { RoomInfo.increment_memberCount_mapkey(roomid, -1L); UserInfo info = UserInfo.getInstance(sessionid); if (info != null) info.checkUpdateHallInfos(); }
info.checkUpdateHallInfos() 将前面更新的所有聊天室信息发送给sessionid指定的客户端。所以客户端看起来的效果就是:首先退出聊天界面进入聊天室选择界面, 接下来聊天室选择界面被刷新。
注意,这里获取info之后需要判断是否为null,用户断线的情况下,用户关联的ViewContext进入关闭状态,这时info将返回null。一般来说,执行View的查询操作之后应该判断是否返回null,避免后续处理产生不必要的异常。
可以这样做一个实验,进入test聊天室后,输入命令
1. .cm on 123456 2. .cm createhall hall0 3. .cm createroom hall0 room0
上述3条命令输入完毕后,退出聊天室,然后重新进入room0聊天室。
输入命令
1. .cm on 123456 2. .cm deleteroom hall0 room0
命令2发送完毕以后,当前用户自己被从聊天室界面踢出去,可以看到与前一个相同的聊天室选择界面,接下来room0立刻被刷新掉。
另外,
cmdmap.put("deletehall", (sessionid, params) -< { ............................................ }
可以删除整个聊天大厅,销毁大厅内所有ChatRoom
-