2.4.1 View部分
-
SessionView
chatroom.view.xml:
<view name="UserInfo" lifecycle="session"> <bind name="name" table="userinfo"> <ref name="nickname" /> </bind> <variable name="recvedmessage" type="ChatMessage" /> <variable name="sendedmessage" type="ChatMessage" /> <variable name="lasterror" type="int" /> </view>
-
<variable name="recvedmessage" type="ChatMessage" />
<variable name="sendedmessage" type="ChatMessage" >
这两个字段用来实现私聊。
用户向当前的ChatRoom发送聊天类Message,这里getMessageTo()返回接受用户id, toid!=-1
chatroom.js:
function sendMessage() { var sendmsg = document.getElementById('sendmsg'); var msg = sendmsg.value.trim(); if (msg.length > 0) context.send(currentChatView, "message=" + getMessageTo() + "," + msg); sendmsg.value = ""; }
服务器ChatRoom收到Message进行处理
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 "message": { .................................. if (-1L == toid) { .................................. } else { UserInfo.getInstance(toid).setRecvedmessage(new ChatMessage(msg, sessionid)); UserInfo.getInstance(sessionid).setSendedmessage(new ChatMessage(msg, toid)); RoomInfo.increment_privateMessages_mapkey(roomid); } return; } .................................. }
UserInfo.getInstance(toid).setRecvedmessage 将消息和发送用户id通告给接收用户
UserInfo.getInstance(sessionid).setSendedmessage 将消息和接收用户id通告回发送用户
chatroom.js:
ctx.register(v100.chatviews.UserInfo, "recvedmessage", function(e) { onReceiveMessage(e.value); }); ctx.register(v100.chatviews.UserInfo, "sendedmessage", function(e) { onSendedMessage(e.value); });
接收用户显示 xxx to you : msg
发送用户显示 you to xxx : msg
-
<bind name="name" table="userinfo">
这个字段用来设置用户nickname
UserInfo.java:
private UserInfo(SessionView.CreateParameter param) { super(param); // bind here bindUserinfo(sessionid); long sessionid = param.getSessionId(); Procedure.execute(ProcedureHelper.nameProcedure("UserInfo init nickname", () -> { xbean.UserInfo userinfo = table.Userinfo.insert(sessionid); if (userinfo != null) userinfo.setNickname(""); return true; })); logintime = System.currentTimeMillis(); checkUpdateHallInfos(); }
用户登入,UserInfo被自动创建,其中
<bind name="name" table="userinfo"> <ref name="nickname" /> </bind>
chatroom.zdb.xml:
<xbean name="UserInfo"> <variable name="nickname" type="string" /> </xbean> <table name="userinfo" key="long" value="UserInfo" />
bindUserinfo建立了name与userinfo表的记录xbean字段nickname的绑定, userinfo表的key对应到这里的sessionid,也就是说,一旦userinfo表中,对应的xbean字段nickname发生改变,这个改变立刻同步到客户端。bindUserinfo时,如果sessionid对应的记录存在,则将记录同步给客户端,作为初始值;如果不存在就不发送,表示没有,这种情况下bind建立的关系依然存在,以后发生了改变再同步到客户端。
接下的存储过程初始化nickname,如果insert成功表示记录并不存在,这种情况下把nickname设置为空串,客户端检测到空串,就应该初始化nickname。如果insert失败,表示记录存在,这种情况下,前面的bindUserinfo会把记录同步到客户端。
总的来说,这是一种典型的bind方式,先bind,然后初始化对应记录,要么记录存在,记录的值被发送出去,要么存储过程初始化了值,导致了改变,由于bind的关系,这个初始化的值被发送出去。
chatroom.js:
ctx.register(v100.chatviews.UserInfo, "name", function(e) { if (e.value.nickname == "") doRename(); else showNickname(); });
客户端检测到name的值发生变化,如果为空串,则调用doRename初始化nickname。
chatroom.js:
function doRename() { var v100 = context[100]; var name = prompt("input name", v100.chatviews.UserInfo.name.nickname); if (name != null && name.length != 0 && name != v100.chatviews.UserInfo.name.nickname) context.send(v100.chatviews.UserInfo, "nickname=" + name); }
初始化名字,改名均用这个方法,获取到名字以后向UserInfo发送Message告知新的name。
UserInfo.java:
protected void onMessage(String message, long sessionid) { final int index = message.indexOf('='); if (-1 == index) return; checkUpdateHallInfos(); final String cmd = message.substring(0, index).trim(); final String param = message.substring(index + 1).trim(); switch (cmd) { case "nickname": Procedure.execute(ProcedureHelper.nameProcedure("onMessage nickname", () -> { xbean.UserInfo info = table.Userinfo.update(sessionid); if (param.equals(info.getNickname())) UserInfo.this._setLasterror(ErrorCodes.EC_NAME_UNMODIFIED); return false; } if (!GlobalId.create("chatserver", param)) { UserInfo.this._setLasterror(ErrorCodes.EC_NAME_EXISTING); return false; } info.setNickname(param); SysInfo.increment_nickNameChange(); return true; })); return; .............................................. } }
这里第一个if判断nickname是否发生改变,这个用来避免客户端漏写检测。
第二个if向GlobalId注册一个这个名字,如果返回false表示重名,报告重名错误,这里ErrorCodes.EC_NAME_EXISTING = 2,GlobalId的create, delete, exists三个方法必须在事务环境中使用, 也就是说写在存储过程内部,这些方法参与事务,也就是说如果事务回滚,分配的名字自动被释放。
注意到两个报错使用了_setLasterror,View的下划线版本的字段设置方法一旦调用立即更新View,向客户端同步数据;非下划线版本在事务环境下,直到事务提交才真正更新View,事务回滚则忽略修改。报告错误使用下划线版本属于通常用法。
setNickname在userinfo表中修改了当前用户的nickname,因为之前已经bind,所以修改成功以后,这个修改后的值通过Userinfo的bind字段name被自动同步到客户端。
如果修改成功,在客户端:
chatroom.js:
ctx.register(v100.chatviews.UserInfo, "name", function(e) { if (e.value.nickname == "") doRename(); else showNickname(); });
这里显示修改后的nickname;
如果修改过程检测到重名:
chatroom.js:
ctx.register(v100.chatviews.UserInfo, "lasterror", function(e) { if (e.value == 2) doRename(); else showErrorFrame(e.value); });
重名返回错误码2,这里要求重新Rename。其它错误显示一个错误页面。
-
<variable name="lasterror" type="int" />
向用户报告错误,错误码定义在:
chatroom.view.xml:
<bean name="ErrorCodes"> <enum name="EC_SUCCEED" value="0" /> <enum name="EC_NAME_UNMODIFIED" value="1" /> <enum name="EC_NAME_EXISTING" value="2" /> <enum name="EC_BAD_ROOM_ID" value="11" /> <enum name="EC_BAD_ARGS" value="12" /> <enum name="EC_BAD_HALL_NAME" value="13" /> <enum name="EC_BAD_ROOM_NAME" value="14" /> </bean>
-