【Python】AdminLTEとWebSocketでチャット機能を作ってみる その3(データ表示、登録)
おはようございます。
昨日に引き続きチャットの実装をしていきます。
今回は登録されているデータの表示と、メッセージ送信時のデータ登録など。
プログラムは前回のものを流用します。
【Python】AdminLTEとWebSocketでチャット機能を作ってみる その2(ログイン機能)
スポンサーリンク
下準備
新たにテーブルを追加して、データを登録しておきます。
テーブル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | -- チャットメンバー createtableTBL_CHATROOM_MEMBER( ROOM_NOint(3)not nullcomment'部屋番号' ,USER_IDvarchar(20)not nullcomment'ユーザーID' ,CREATE_USERvarchar(20)comment'作成者' ,CREATE_DATEdatetimecomment'作成日時' ,UPDATE_USERvarchar(20)comment'更新者' ,UPDATE_DATEdatetimecomment'更新日時' ,constraintTBL_CHATROOM_MEMBER_PKCprimary key(ROOM_NO,USER_ID) )comment'チャットメンバー'; -- ユーザー関係マスタ createtableMST_USER_RELATION( USER_IDvarchar(20)not nullcomment'ユーザーID' ,USER_ID2varchar(20)not nullcomment'ユーザーID2:ユーザーID' ,ROOM_NOint(3)comment'部屋番号' ,STATUSint(1)not nullcomment'状態' ,CREATE_USERvarchar(20)comment'作成者' ,CREATE_DATEdatetimecomment'作成日時' ,UPDATE_USERvarchar(20)comment'更新者' ,UPDATE_DATEdatetimecomment'更新日時' ,constraintMST_USER_RELATION_PKCprimary key(USER_ID,USER_ID2) )comment'ユーザー関係マスタ'; -- チャットメッセージ createtableTBL_CHATMESSAGE( ROOM_NOint(3)not nullcomment'部屋番号' ,MESSAGE_NOint(10)not nullcomment'メッセージ番号' ,USER_IDvarchar(20)not nullcomment'ユーザーID' ,MESSAGEvarchar(1000)not nullcomment'メッセージ内容' ,SEND_DATEdatetimecomment'送信日時' ,constraintTBL_CHATMESSAGE_PKCprimary key(ROOM_NO,MESSAGE_NO) )comment'チャットメッセージ'; -- チャットルーム createtableMST_ROOM( ROOM_NOint(3)not nullcomment'部屋番号' ,ROOM_NAMEvarchar(30)not nullcomment'部屋名称' ,ICONvarchar(20)comment'ICON:画像ファイル名' ,LAST_VIEW_DATEdatetimecomment'最終参照日時' ,CREATE_USERvarchar(20)comment'作成者' ,CREATE_DATEdatetimecomment'作成日時' ,UPDATE_USERvarchar(20)comment'更新者' ,UPDATE_DATEdatetimecomment'更新日時' ,constraintMST_ROOM_PKCprimary key(ROOM_NO) )comment'チャットルーム'; |
データ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | -- MSTルーム DELETEFROMMST_ROOM; INSERTINTOMST_ROOMVALUES('1','個別',NULL,NULL,'INIT',NULL,NULL,NULL); INSERTINTOMST_ROOMVALUES('2','グループ',NULL,NULL,'INIT',NULL,NULL,NULL); -- MSTユーザー関係 DELETEFROMMST_USER_RELATION; INSERTINTOMST_USER_RELATIONVALUES('001','002','0','1','INIT',NULL,NULL,NULL); INSERTINTOMST_USER_RELATIONVALUES('001','003','0','1','INIT',NULL,NULL,NULL); INSERTINTOMST_USER_RELATIONVALUES('001','004','0','1','INIT',NULL,NULL,NULL); INSERTINTOMST_USER_RELATIONVALUES('001','005','1','1','INIT',NULL,NULL,NULL); -- MSTチャットメンバー DELETEFROMTBL_CHATROOM_MEMBER; INSERTINTOTBL_CHATROOM_MEMBERVALUES('1','001','INIT',NULL,NULL,NULL); INSERTINTOTBL_CHATROOM_MEMBERVALUES('1','005','INIT',NULL,NULL,NULL); INSERTINTOTBL_CHATROOM_MEMBERVALUES('2','001','INIT',NULL,NULL,NULL); INSERTINTOTBL_CHATROOM_MEMBERVALUES('2','002','INIT',NULL,NULL,NULL); INSERTINTOTBL_CHATROOM_MEMBERVALUES('2','003','INIT',NULL,NULL,NULL); INSERTINTOTBL_CHATROOM_MEMBERVALUES('2','004','INIT',NULL,NULL,NULL); -- メッセージ DELETEFROMTBL_CHATMESSAGE; INSERTINTOTBL_CHATMESSAGEVALUES('1','0','005','そら最近どうしてる?','2018-09-25 02:00:00'); INSERTINTOTBL_CHATMESSAGEVALUES('1','1','001','相変わらずだよ。\nあいつらの面倒で手一杯でさ。','2018-09-25 02:05:00'); INSERTINTOTBL_CHATMESSAGEVALUES('1','2','005','一番のお兄さんだから大変ね。\n私は一人で快適な暮らしを送っているわ(^^♪','2018-09-25 05:37:00'); INSERTINTOTBL_CHATMESSAGEVALUES('1','3','001','え、なにそれ自慢ですか?','2018-09-25 06:10:00'); |
画面の修正
コンタクトリスト、メッセージなどをDBから取得して表示するように修正。
main.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"xml:lang="ja"lang="ja"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible"content="IE=edge"> <meta http-equiv="content-type"content="text/html; charset=UTF-8"> <title>チャットサンプル</title> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"name="viewport"> <link rel="stylesheet"href="https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <link rel="stylesheet"href="https:////maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"> <link rel="stylesheet"href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic"> <link rel="stylesheet"href="{{ static_url('css/AdminLTE.min.css') }}"> <link rel="stylesheet"href="{{ static_url('css/style.css') }}"> <link rel="stylesheet"href="{{ static_url('css/skins/skin-blue.min.css') }}"> </head> <body class="hold-transition fixed"> <form id="logoutForm"action="/logout"method="get"> {% module xsrf_form_html() %} </form> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- ヘッダ情報 --> <div class="navbar-header"style="padding:15px;"> <input type="hidden"id="user_id"value="{{ user_id }}" /> <input type="hidden"id="user_name"value="{{ user_name }}" /> ユーザー名:{{ user_name }} </div> <!-- リストの配置 --> <ul class="nav navbar-nav"> <li class="active"><a href="#">チャット</a></li> <li><a href="#">メニュー1</a></li> <li><a href="#">メニュー2</a></li> </ul> <!-- ボタン --> <button type="button"class="pull-right btn btn-default navbar-btn"onclick="logout();"> ログアウト <span class="fa fa-sign-out"></span> </button> </div> </nav> <section class="content container-fluid"> <div class="row"> <div class="col-xs-8"> <div class="row"> <div class="col-xs-8"> <div id="chat-panel"class="box box-warning direct-chat direct-chat-warning box-solid"style="display:none;"> <input type="hidden"id="room_no"value="{{ room_no }}" /> <div class="box-header with-border"> <h3 class="box-title">チャットメッセージ</h3> <div class="box-tools pull-right"> <span id="status"class="status"></span> <span data-toggle="tooltip"title="3 New Messages"class="badge bg-yellow">3</span> <button type="button"class="btn btn-box-tool"data-widget="collapse"><i class="fa fa-minus"></i></button> <button type="button"class="btn btn-box-tool"data-toggle="tooltip"title="Contacts"data-widget="chat-pane-toggle"> <i class="fa fa-comments"></i> </button> <button type="button"class="btn btn-box-tool"data-widget="remove"><i class="fa fa-times"></i></button> </div> </div> <div class="box-body"> <div class="direct-chat-messages"> {% for msg in chat_messages %} {% if msg['user_id'] == user_id %} <div class="direct-chat-msg right"> <div class="direct-chat-info clearfix"> <span class="direct-chat-name pull-right">{{ msg['user_name'] }}</span> <span class="direct-chat-timestamp pull-left">{{ msg['send_date'] }}</span> </div> <img class="direct-chat-img"src="static/img/{{ msg['icon'] }}"alt="message user image"> <div class="direct-chat-text"> {% autoescape None %} {{ msg['message'] }} </div> </div> {% else %} <div class="direct-chat-msg"> <div class="direct-chat-info clearfix"> <span class="direct-chat-name pull-left">{{ msg['user_name'] }}</span> <span class="direct-chat-timestamp pull-right">{{ msg['send_date'] }}</span> </div> <img class="direct-chat-img"src="static/img/{{ msg['icon'] }}"alt="message user image"> <div class="direct-chat-text"> {{ msg['message'] }} </div> </div> {% end %} {% end %} </div> <div class="direct-chat-contacts"> <ul class="contacts-list"> {% for user in relation_users %} <li> <a href="#"> <img class="contacts-list-img"src="static/img/{{ user['icon'] }}"alt="User Image"> <div class="contacts-list-info"> <span class="contacts-list-name"> {{ user['user_name'] }} <small class="contacts-list-date pull-right">{{ user['update_date'] }}</small> </span> <span class="contacts-list-msg">{{ user['message'] }}</span> </div> </a> </li> {% end %} </ul> </div> </div> <div class="box-footer"> <form action="#"method="post"> <div class="input-group"> <textarea id="message"type="text"name="message"placeholder="メッセージを入力してください"class="form-control"></textarea> <span class="input-group-btn"> <button id="sendButton"type="button"class="btn btn-warning btn-flat btn-sm">送信</button> </span> </div> </form> </div> </div> </div> </div> </div> </div> </section> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.19/jquery-ui.min.js"></script> <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.js"></script> <script src="{{ static_url('js/adminlte.min.js') }}"></script> <script src="{{ static_url('js/script.js') }}"></script> <script> $(document).ready(function(){ initialize(); }); </script> </body> </html> |
プログラム
新規追加
チャットメッセージ用のクラスを追加
Dao/TblChatMessageDao.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | fromDao.SqlClient importSqlClient classTblChatMessageDao(SqlClient): """ TBLチャットメッセージDAOクラス """ defselect_message_by_room_no(self,room_no): """ チャットメッセージを取得します :param room_no: :return: """ sql="SELECT" sql+=" U.USER_NAME" sql+=" , U.ICON" sql+=" , M.* " sql+="FROM" sql+=" TBL_CHATMESSAGE M" sql+=" LEFT OUTER JOIN MST_USER U ON (" sql+=" M.USER_ID = U.USER_ID" sql+=" )" sql+="WHERE" sql+=" ROOM_NO = %s " sql+="ORDER BY" sql+=" SEND_DATE" result=[] data=super().select(sql,[room_no]) forrindata: result.append(self.mapping_data(r)) returnresult defselect_next_message_no(self,room_no): """ 部屋番号毎の次のメッセージ番号を取得します :param room_no: :return: """ sql="SELECT MAX(MESSAGE_NO) +1 AS MESSAGE_NO FROM TBL_CHATMESSAGE WHERE ROOM_NO = %s" data=super().select_one(sql,[room_no]) returndata["MESSAGE_NO"] definsert_message(self,data): """ データを登録します :param data: :return: """ message_no=self.select_next_message_no(data["room_no"]) record=[ data["room_no"] ,message_no ,data["user_id"] ,data["message"] ,data["send_date"] ] sql="INSERT INTO TBL_CHATMESSAGE VALUES (%s,%s,%s,%s,%s)" super().execute(sql,record) defmapping_data(self,record): """ レコードをマッピングします :param record: :return: """ dic=dict() dic['room_no']=record.get('ROOM_NO') dic['message_no']=record.get('MESSAGE_NO') dic['user_id']=record.get('USER_ID') dic['message']=self.escape_newline(record.get('MESSAGE')) dic['send_date']=self.parse_date(record.get('SEND_DATE')) dic['user_name']=record.get('USER_NAME') dic['icon']=record.get('ICON') returndic |
既存クラスの修正
コンタクトリストを取得するように修正
Dao/MstUserDao.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | fromDao.SqlClient importSqlClient classMstUserDao(SqlClient): """ MSTユーザーDAOクラス """ defselect_user(self,user_id): """ ユーザIDを指定してデータを取得します :param user_id: :return: """ sql="SELECT * FROM MST_USER WHERE USER_ID = %s" data=super().select(sql,[user_id]) iflen(data)>0: returnself.mapping_data(data[0]) returnNone defselect_relation_user(self,user_id): """ 関係ユーザーを取得します :param user_id: :return: """ sql="SELECT" sql+=" R.ROOM_NO" sql+=" , M.ROOM_NAME" sql+=" , U.* " sql+="FROM" sql+=" MST_USER_RELATION R" sql+=" LEFT OUTER JOIN MST_ROOM M ON (" sql+=" M.ROOM_NO = R.ROOM_NO" sql+=" )" sql+=" LEFT OUTER JOIN MST_USER U ON (" sql+=" U.USER_ID = R.USER_ID2" sql+=" ) " sql+="WHERE" sql+=" R.USER_ID = %s" result=[] data=super().select(sql,[user_id]) forrindata: result.append(self.mapping_data(r)) returnresult defmapping_data(self,record): """ レコードをマッピングします :param record: :return: """ dic=dict() dic['user_id']=record.get('USER_ID') dic['user_name']=record.get('USER_NAME') dic['icon']=record.get('ICON') dic['message']=record.get('MESSAGE') dic['create_user']=record.get('CREATE_USER') dic['create_date']=self.parse_date(record.get('CREATE_DATE')) dic['update_user']=record.get('UPDATE_USER') dic['update_date']=self.parse_date(record.get('UPDATE_DATE')) dic['room_no']=record.get('ROOM_NO') dic['room_name']=record.get('ROOM_NAME') returndic |
メイン、WEBソケットクラスの修正
Main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | classMainHandler(AuthBaseHandler): """ 初期表示処理 """ definitialize(self): logging.info("[MainHandler] initialize") @tornado.web.authenticated defget(self): logging.info("[MainHandler] get") user_id=self.get_current_user() user_dao=MstUserDao() user=user_dao.select_user(user_id) user_name=user['user_name'] relation_users=user_dao.select_relation_user(user_id) message_dao=TblChatMessageDao() chat_messages=message_dao.select_message_by_room_no(1) self.render("main.html", user_id=user_id, user_name=user_name, room_no=1, relation_users=relation_users, chat_messages=chat_messages) classChatHandler(WebSocketHandler): """ チャット処理 """ defopen(self): logging.info("[ChatHandler] open") ifselfnotinclient: client.append(self) defon_message(self,message): logging.info("[ChatHandler] on_message : "+message) # データ登録 data=json.loads(message) message_dao=TblChatMessageDao() message_dao.insert_message(data) # ユーザー取得 user_dao=MstUserDao() result=user_dao.select_user(data["user_id"]) # ユーザ情報にメッセージをチャット情報を追加 result["room_no"]=data["room_no"] result["message"]=data["message"] result["send_date"]=data["send_date"] forcl inclient: cl.write_message(json.dumps(result,ensure_ascii=False)) defon_close(self): logging.info("[ChatHandler] on_close") ifselfinclient: client.remove(self) |
クライアント側の処理も修正。
script.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | // ソケット varsocket=newWebSocket('ws://'+location.host+'/chat'); moment.lang('ja',{ weekdays:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"], weekdaysShort:["日","月","火","水","木","金","土"], }); /** * ログアウト処理. */ functionlogout(){ $("#logoutForm").submit(); } /** * 初期処理. */ functioninitialize(){ // 通信ソケットオープン socket.onopen=function(data){ $("#status").css("color","#FFFFFF"); $("#status").text(" [オンライン]"); } // 通信ソケットクローズ socket.onclose=function(){ $("#status").css("color","#999999"); $("#status").text(" [オフライン]") } // メッセージ受信 socket.onmessage=function(e){ console.log(e.data); vardata=JSON.parse(e.data); $(".direct-chat-messages").append(createMessage(data)); $(".direct-chat-messages").animate({ scrollTop:$(".direct-chat-messages")[0].scrollHeight },500); } // ボタンにイベントを追加 $("#sendButton").click(function(){ sendMessage(); }); // チャットの表示を一番下に $("#chat-panel").show(); $(".direct-chat-messages")[0].scrollTop=$(".direct-chat-messages")[0].scrollHeight; } /** * メッセージを送信. */ functionsendMessage(){ // メッセージが未入力の場合は送信しない if(!$("#message").val()){ return; } // 送信日時 varnow=newmoment(); varsend_date=now.format("YYYY-MM-DD HH:mm:ss") // パラメータ varparam={ "user_id":$("#user_id").val() ,"room_no":$("#room_no").val() ,"message":$("#message").val() ,"send_date":send_date } socket.send(JSON.stringify(param)); } /** * メッセージタグを作成して返します. */ functioncreateMessage(data){ // 本人かどうかを判定 varisSelf=$("#user_id").val()==data.user_id; varmsgDiv=$("<div>",{ "class":"direct-chat-text" ,"html":data.message.replace(/\n/g,"<BR>") }); varimg=$("<img>",{ "class":"direct-chat-img" ,"src":"static/img/"+data.icon }); varclazz="pull-right"; if(isSelf){ clazz="pull-left"; } vardt=$("<span>",{ "class":"direct-chat-timestamp "+clazz ,"text":moment(data.send_date,"YYYY-MM-DD HH:mm:ss").format("YYYY/MM/DD(ddd) HH:mm") }); varclazz="pull-left"; if(isSelf){ clazz="pull-right"; } varname=$("<span>",{ "class":"direct-chat-name "+clazz ,"text":data.user_name }); varinfo=$("<div>",{ "class":"direct-chat-info clearfix" }); varclazz=""; if(isSelf){ clazz="right"; } varchatMsg=$("<div>",{ "class":"direct-chat-msg "+clazz }); // タグを作成していく info.append(name); info.append(dt); chatMsg.append(info); chatMsg.append(img); chatMsg.append(msgDiv); returnchatMsg; } |
起動してみる
別々のユーザで(別セッション)ログインし、それぞれでメッセージを送信してみました。
まとめ
なんとなく出来上がってきた感じがしますね。
現状は、固定のチャットルームとしていますが、
次回はそこらへんの実装を進めていきたいと思います。
メッセージの配信部分については、
ソケット通信に接続しているセッション全てに配信して
クライアント側で処理を振り分けるか、サーバー側で配信先を振り分けるか悩んでいます。
まだまだ WebSocket については知識が浅く、正解が分かりませんが色々試してみます。
ではでは。
ディスカッション
コメント一覧
まだ、コメントがありません