先下載github代碼,下面的操做,都是基於這個版原本的!javascript
https://github.com/987334176/Intelligent_toy/archive/v1.4.zipphp
注意:因爲涉及到版權問題,此附件沒有圖片和音樂。請參考連接,手動採集一下!css
請參考連接:html
http://www.javashuo.com/article/p-splctlzd-t.html前端
Redis項目並無正式支持Windows。然而,微軟OpenTeaGeo集團開發並維護了以WIN64爲目標的Windows端口。連接以下:java
https://github.com/MicrosoftArchive/redis/releasesjquery
目前最新版本是3.2ios
完整下載連接以下:git
https://github.com/MicrosoftArchive/redis/releases/download/win-3.2.100/Redis-x64-3.2.100.msigithub
開始安裝
注意:要勾選添加到系統環境變量
默認端口是6379
設置最大內存,這裏不設置。表示不限制內存大小!
安裝完成後,打開cmd窗口,運行命令
d: cd D:\Program Files\Redis redis-server.exe redis.windows.conf redis-cli
效果以下:
使用Python操做redis,須要安裝模塊redis
pip install redis
接下來的操做,會將消息數量,存在redis中!
若是有消息來了,須要在底部選項卡中的消息按鈕,顯示角標。好比這樣:
還有 好友列表,也要顯示角標。誰發送了幾條消息,好比這樣:
進入flask項目,修改setting.py,增長redis配置
import pymongo import os import redis # 數據庫配置 client = pymongo.MongoClient(host="127.0.0.1", port=27017) MONGO_DB = client["bananabase"] REDIS_DB = redis.Redis(host="127.0.0.1",port=6379) RET = { # 0: false 2: True "code": 0, "msg": "", # 提示信息 "data": {} } XMLY_URL = "http://m.ximalaya.com/tracks/" # 喜馬拉雅連接 CREATE_QR_URL = "http://qr.liantu.com/api.php?text=" # 生成二維碼API # 文件目錄 AUDIO_FILE = os.path.join(os.path.dirname(__file__), "audio") # 音頻 AUDIO_IMG_FILE = os.path.join(os.path.dirname(__file__), "audio_img") # 音頻圖片 DEVICE_CODE_PATH = os.path.join(os.path.dirname(__file__), "device_code") # 二維碼 CHAT_FILE = os.path.join(os.path.dirname(__file__), "chat") # 聊天 # 百度AI配置 APP_ID = "117912345" API_KEY = "3v3igzCkVFUDwFByNEE12345" SECRET_KEY = "jRnwLE7kzC1aRi2FD10OQY3y9O12345" SPEECH = { "spd": 4, 'vol': 5, "pit": 8, "per": 4 }
進入utils文件夾,建立文件 chat_redis.py
from setting import REDIS_DB import json def save_msg(小甜甜,xiao): """ key: xiao { 小甜甜:5, 小豆芽:4 } :return: """ res = json.loads(REDIS_DB.get(xiao)) res["小甜甜"] = 1 if res: pass REDIS_DB.set(xiao,json.dumps(res))
看下面的示例圖:
修改 chat_redis.py
from setting import REDIS_DB import json def save_msg(sender, to_user): # 保存消息 # 1.查詢一下xiao的Redis是否有數據 user_msg_redis = REDIS_DB.get(to_user) if user_msg_redis: # 2.將xiao的數據反序列化成字典 { sender : n } user_msg_dict = json.loads(user_msg_redis) # 3.判斷有沒有 sender 的用戶發來的消息數量 if user_msg_dict.get(sender): # 數量加1 user_msg_dict[sender] += 1 else: # 第一次,初始值爲1 user_msg_dict[sender] = 1 # 4.若是xiao是剛創建好的用戶,他是沒有消息的,字典是空 else: user_msg_dict = {sender: 1} # 5.序列化用戶消息字典user_msg_dict user_msg_redis = json.dumps(user_msg_dict) # 6.存回Redis REDIS_DB.set(to_user, user_msg_redis) def get_msg_list(user): # 獲取消息 user_msg_redis = REDIS_DB.get(user) if user_msg_redis: user_msg_dict = json.loads(user_msg_redis) # 統計數量 user_msg_dict["count"] = sum(user_msg_dict.values()) else: user_msg_dict = {"count":0} return user_msg_dict def get_user_msg_one(sender, to_user): # 獲取一條消息 user_msg_redis = REDIS_DB.get(to_user) if user_msg_redis: user_msg_dict = json.loads(user_msg_redis) if user_msg_dict.get(sender): return user_msg_dict.get(sender)
修改 im_serv.py
from flask import Flask, request from geventwebsocket.websocket import WebSocket from geventwebsocket.handler import WebSocketHandler from gevent.pywsgi import WSGIServer import json, os from uuid import uuid4 from setting import AUDIO_FILE,CHAT_FILE from serv import content from utils import baidu_ai from utils import chat_redis import setting from bson import ObjectId import time app = Flask(__name__) user_socket_dict = {} # 空字典,用來存放用戶名和發送消息 @app.route("/toy/<tid>") def toy(tid): # 玩具鏈接 # 獲取請求的WebSocket對象 user_socket = request.environ.get("wsgi.websocket") # type:WebSocket if user_socket: # 設置鍵值對 user_socket_dict[tid] = user_socket print(user_socket_dict) # {'123456': <geventwebsocket.websocket.WebSocket object at 0x00000176ABD92E18>} file_name = "" to_user = "" # 循環,接收消息 while True: msg = user_socket.receive() if type(msg) == bytearray: file_name = f"{uuid4()}.wav" file_path = os.path.join(CHAT_FILE, file_name) with open(file_path, "wb") as f: f.write(msg) else: msg_dict = json.loads(msg) to_user = msg_dict.get("to_user") msg_type = msg_dict.get("msg_type") if to_user and file_name: other_user_socket = user_socket_dict.get(to_user) if msg_type == "ai": q = baidu_ai.audio2text(file_path) print(q) ret = baidu_ai.my_nlp(q, tid) other_user_socket.send(json.dumps(ret)) else: send_str = { "code": 0, "from_user": tid, "msg_type": "chat", "data": file_name } if other_user_socket: # 當websocket鏈接存在時 chat_redis.save_msg(tid, to_user) # 保存消息到redis # 發送數據 other_user_socket.send(json.dumps(send_str)) else: # 離線消息 chat_redis.save_msg(tid, to_user) # 保存聊天記錄到MongoDB _add_chat(tid, to_user, send_str.get("data")) to_user = "" file_name = "" @app.route("/app/<uid>") def user_app(uid): # 手機app鏈接 user_socket = request.environ.get("wsgi.websocket") # type:WebSocket if user_socket: user_socket_dict[uid] = user_socket # { uid : websocket} print(user_socket_dict) file_name = "" to_user = "" while True: # 手機聽歌 把歌曲發送給 玩具 1.將文件直接發送給玩具 2.將當前聽的歌曲名稱或ID發送到玩具 msg = user_socket.receive() if type(msg) == bytearray: # 判斷類型爲bytearray file_name = f"{uuid4()}.amr" # 文件後綴爲amr,安卓和ios通用 file_path = os.path.join(CHAT_FILE, file_name) # 存放在chat目錄 print(msg) with open(file_path, "wb") as f: f.write(msg) # 寫入文件 # 將amr轉換爲mp3,由於html中的audio不支持amr os.system(f"ffmpeg -i {file_path} {file_path}.mp3") else: msg_dict = json.loads(msg) to_user = msg_dict.get("to_user") # 獲取目標用戶 if msg_dict.get("msg_type") == "music": other_user_socket = user_socket_dict.get(to_user) send_str = { "code": 0, "from_user": uid, "msg_type": "music", "data": msg_dict.get("data") } other_user_socket.send(json.dumps(send_str)) # res = content._content_one(content_id) if file_name and to_user: # 若是文件名和發送用戶同上存在時 # 查詢玩具信息 res = setting.MONGO_DB.toys.find_one({"_id": ObjectId(to_user)}) # 獲取friend_remark fri = [i.get("friend_remark") for i in res.get("friend_list") if i.get("friend_id") == uid][0] msg_file_name = baidu_ai.text2audio(f"你有來自{fri}的消息") # 獲取websocket對象 other_user_socket = user_socket_dict.get(to_user) # 構造數據 send_str = { "code": 0, "from_user": uid, "msg_type": "chat", # 聊天類型 # 後綴必須是mp3的 "data": msg_file_name } # 發送數據給前端頁面 other_user_socket.send(json.dumps(send_str)) # 添加聊天記錄到數據庫 _add_chat(uid, to_user, f"{file_name}.mp3") # 最後必定要清空這2個變量,不然形成混亂 file_name = "" to_user = "" def _add_chat(sender, to_user, msg): # 添加聊天記錄到數據庫 chat_window = setting.MONGO_DB.chat.find_one({"user_list": {"$all": [sender, to_user]}}) if not chat_window.get("chat_list"): chat_window["chat_list"] = [{ "sender": sender, "msg": msg, "updated_at": time.time(), }] res = setting.MONGO_DB.chat.update_one({"_id": ObjectId(chat_window.get("_id"))}, {"$set": chat_window}) else: chat = { "sender": sender, "msg": msg, "updated_at": time.time(), } res = setting.MONGO_DB.chat.update_one({"_id": ObjectId(chat_window.get("_id"))}, {"$push": {"chat_list": chat}}) return res if __name__ == '__main__': # 建立一個WebSocket服務器 http_serv = WSGIServer(("0.0.0.0", 9528), app, handler_class=WebSocketHandler) # 開始監聽HTTP請求 http_serv.serve_forever() ''' { "code": 0, "from_user": uid, # APP用戶id "data": music_name # 歌曲名 } '''
重啓 im_serv.py
打開網頁,讓 小甜甜 開機
點擊 開始廢話,使用麥克風說: 給小魚 發消息。再點擊發送語音!
此時,錄製消息按鈕後面,會出現 對方id
注意:小魚 表示玩具對主人(xiao)的稱呼
再點擊 錄製消息,說: hello。最後點擊發送語音消息!
進入redis,查看消息,將網頁中的 錄製消息按鈕後面的id複製過來
查看用戶id的數據,能夠發現數量爲1
127.0.0.1:6379> get 5b9bb768e1253281608e96eb "{\"5ba0f1f2e12532418089bf88\": 1}" 127.0.0.1:6379>
那麼redis中的數據有了,就能夠在APP中渲染了
進入HBuilder項目MyApp,修改 index.html,增長角標
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <title></title> <script src="js/mui.js"></script> <link href="css/mui.min.css" rel="stylesheet" /> </head> <body> <!--底部選項卡--> <nav class="mui-bar mui-bar-tab"> <a class="mui-tab-item mui-active" id="index"> <span class="mui-icon mui-icon-home"></span> <span class="mui-tab-label">首頁</span> </a> <a class="mui-tab-item" id="message"> <span class="mui-icon mui-icon-chat"> <span class="mui-badge mui-badge-red" id="msg_count">0</span> </span> <span class="mui-tab-label">消息</span> </a> <a class="mui-tab-item"> <span class="mui-icon mui-icon-email"></span> <span class="mui-tab-label">郵件</span> </a> <a class="mui-tab-item" id="login"> <span class="mui-icon mui-icon-gear"></span> <span class="mui-tab-label">設置</span> </a> </nav> </body> <script type="text/javascript" charset="utf-8"> var ws = null; // websocket對象 mui.init({ subpages: [{ url: "main.html", id: "main.html", styles: window.styles }] }); mui.plusReady(function() { // console.log(JSON.stringify(plus.webview.currentWebview())) if(plus.storage.getItem("user")) { // 判斷是否登陸 console.log('已結登陸了!'); //鏈接websocket鏈接 ws = new WebSocket("ws://" + window.ws_serv + "/app/" + plus.storage.getItem("user")) // 發送post請求 console.log(window.serv + "/get_msg_list"); mui.post( // 訪問消息列表 window.serv + "/get_msg_list", { user_id: plus.storage.getItem("user") }, function(data) { console.log(JSON.stringify(data)); msg_data = data.data; // 修改消息選項卡的角標數字 document.getElementById("msg_count").innerText = msg_data.count; } ); // 客戶端接收服務端數據時觸發 ws.onmessage = function() {}; } // 自動重連 ws.onclose = function() { window.location.reload(); } }); // 消息 document.getElementById("message").addEventListener("tap", function() { mui.openWindow({ url: "message.html", id: "message.html", styles: window.styles, extras: { // 傳輸用戶id,給message.html user_id: plus.storage.getItem("user") } }) }); document.getElementById("index").addEventListener("tap", function() { mui.openWindow({ url: "main.html", id: "main.html", styles: window.styles }) }) document.getElementById("login").addEventListener("tap", function() { // 自動登陸,判斷storage中的user存在,就跳轉到user_info,不然跳轉login if(plus.storage.getItem("user")) { mui.openWindow({ url: "user_info.html", id: "user_info.html", styles: window.styles, extras: { user_id: plus.storage.getItem("user") } }) } else { mui.openWindow({ url: "login.html", id: "login.html", styles: window.styles }) } }) document.addEventListener("login", function(data) { // fire事件接收消息,使用data.detail // index是爲作顯示區分 mui.toast("index" + data.detail.msg) }); document.addEventListener("send_music", function(data) { //監聽send_music事件 var music_name = data.detail.music_name; //獲取player.html使用fire發送的music_name值 var toy_id = data.detail.toy_id; //獲取發送的玩具id send_str = { //構造數據 data: music_name, to_user: toy_id, // 目標用戶,這裏統一格式 msg_type: "music", // 類型爲音樂 } // 發送數據給後端,注意要json序列化 ws.send(JSON.stringify(send_str)); }); document.addEventListener("send_msg", function(data) { //發送消息 var filename = data.detail.filename var to_user = data.detail.to_user send_str = { to_user: to_user } ws.send(JSON.stringify(send_str)) plus.io.resolveLocalFileSystemURL(filename, function(entry) { // 可經過entry對象操做test.html文件 entry.file(function(file) { // FileReader文件系統中的讀取文件對象,用於獲取文件的內容 var fileReader = new plus.io.FileReader(); // alert("getFile:" + JSON.stringify(file)); // readAsDataURL: 以URL編碼格式讀取文件數據內容 fileReader.readAsDataURL(file, 'utf-8'); // onloadend: 文件讀取操做完成時的回調函數 fileReader.onloadend = function(evt) { console.log(evt.target.result); var b = dataURLtoBlob(evt.target.result); ws.send(b); // 發送blob數據 } // alert(file.size + '--' + file.name) }); }); }) function dataURLtoBlob(dataurl) { // 數據轉換爲Blob // 邏輯很複雜,這裏不解釋了。直接用就能夠了! var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); while(n--) { u8arr[n] = bstr.charCodeAt(n); } var a = new Blob([u8arr], { type: mime }); return a } </script> </html>
進入 flask項目,修改 serv-->chat.py
from flask import Blueprint, request, jsonify from setting import MONGO_DB from setting import RET from bson import ObjectId from utils import chat_redis cht = Blueprint("cht", __name__) @cht.route("/chat_list", methods=["POST"]) def chat_list(): # 聊天記錄列表 user_id = request.form.get("user_id") friend_id = request.form.get("friend_id") print(friend_id) chat_window = MONGO_DB.chat.find_one({"user_list": {"$all": [user_id, friend_id]}}) fri = MONGO_DB.toys.find_one({"_id": ObjectId(friend_id)}) baby_name = fri.get("baby_name") cl = chat_window.get("chat_list") RET["code"] = 0 RET["msg"] = baby_name RET["data"] = cl return jsonify(RET) @cht.route("/get_msg", methods=["POST"]) def get_msg(): # 獲取聊天語言文件 user_id = request.form.get("user_id") sender = request.form.get("sender") chat_window = MONGO_DB.chat.find_one({"user_list": {"$all": [user_id, sender]}}) new_msg = chat_window.get("chat_list")[-1] RET["code"] = 0 RET["msg"] = "" RET["data"] = new_msg.get("msg") return jsonify(RET) @cht.route("/get_msg_list", methods=["POST"]) def get_msg_list(): user_id = request.form.get("user_id") user_msg_dict = chat_redis.get_msg_list(user_id) RET["code"] = 0 RET["msg"] = "" RET["data"] = user_msg_dict return jsonify(RET)
重啓 manager.py
打開模擬器,關閉裏面的HBuilder進程,從新開啓,效果以下:
效果就完成了!
再來作 聊天窗口,顯示角標。上面演示時,小甜甜 給 小魚(主人) 發送了一條消息。
那麼下圖中的紫色框後面,應該顯示數字1
那麼如何實現呢?答案很簡單,使用mui.openWindows打開message.html頁面時,給它傳一個參數msg_data。
參數大概是這個樣子
"data":{"5ba0f1f2e12532418089bf88":1,"count":1}
5ba0f1f2e12532418089bf88 是 小甜甜 的_id。在toys表中能夠查詢!
修改 index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <title></title> <script src="js/mui.js"></script> <link href="css/mui.min.css" rel="stylesheet" /> </head> <body> <!--底部選項卡--> <nav class="mui-bar mui-bar-tab"> <a class="mui-tab-item mui-active" id="index"> <span class="mui-icon mui-icon-home"></span> <span class="mui-tab-label">首頁</span> </a> <a class="mui-tab-item" id="message"> <span class="mui-icon mui-icon-chat"> <span class="mui-badge mui-badge-red" id="msg_count">0</span> </span> <span class="mui-tab-label">消息</span> </a> <a class="mui-tab-item"> <span class="mui-icon mui-icon-email"></span> <span class="mui-tab-label">郵件</span> </a> <a class="mui-tab-item" id="login"> <span class="mui-icon mui-icon-gear"></span> <span class="mui-tab-label">設置</span> </a> </nav> </body> <script type="text/javascript" charset="utf-8"> var ws = null; // websocket對象 var msg_data = null; // 消息數據 mui.init({ subpages: [{ url: "main.html", id: "main.html", styles: window.styles }] }); mui.plusReady(function() { // console.log(JSON.stringify(plus.webview.currentWebview())) if(plus.storage.getItem("user")) { // 判斷是否登陸 console.log('已結登陸了!'); //鏈接websocket鏈接 ws = new WebSocket("ws://" + window.ws_serv + "/app/" + plus.storage.getItem("user")) // 發送post請求 console.log(window.serv + "/get_msg_list"); mui.post( // 訪問消息列表 window.serv + "/get_msg_list", { user_id: plus.storage.getItem("user") }, function(data) { console.log(JSON.stringify(data)); // {"code":0,"data":{"5ba0f1f2e12532418089bf88":1,"count":1},"msg":""} msg_data = data.data; // 修改消息選項卡的角標數字 document.getElementById("msg_count").innerText = msg_data.count; } ); // 客戶端接收服務端數據時觸發 ws.onmessage = function() {}; } // 自動重連 ws.onclose = function() { window.location.reload(); } }); // 消息 document.getElementById("message").addEventListener("tap", function() { mui.openWindow({ url: "message.html", id: "message.html", styles: window.styles, extras: { // 傳輸用戶id,給message.html user_id: plus.storage.getItem("user"), msg_data: msg_data, // "data":{"5ba0f1f2e12532418089bf88":1,"count":1} } }) }); document.getElementById("index").addEventListener("tap", function() { mui.openWindow({ url: "main.html", id: "main.html", styles: window.styles }) }) document.getElementById("login").addEventListener("tap", function() { // 自動登陸,判斷storage中的user存在,就跳轉到user_info,不然跳轉login if(plus.storage.getItem("user")) { mui.openWindow({ url: "user_info.html", id: "user_info.html", styles: window.styles, extras: { user_id: plus.storage.getItem("user") } }) } else { mui.openWindow({ url: "login.html", id: "login.html", styles: window.styles }) } }) document.addEventListener("login", function(data) { // fire事件接收消息,使用data.detail // index是爲作顯示區分 mui.toast("index" + data.detail.msg) }); document.addEventListener("send_music", function(data) { //監聽send_music事件 var music_name = data.detail.music_name; //獲取player.html使用fire發送的music_name值 var toy_id = data.detail.toy_id; //獲取發送的玩具id send_str = { //構造數據 data: music_name, to_user: toy_id, // 目標用戶,這裏統一格式 msg_type: "music", // 類型爲音樂 } // 發送數據給後端,注意要json序列化 ws.send(JSON.stringify(send_str)); }); document.addEventListener("send_msg", function(data) { //發送消息 var filename = data.detail.filename var to_user = data.detail.to_user send_str = { to_user: to_user } ws.send(JSON.stringify(send_str)) plus.io.resolveLocalFileSystemURL(filename, function(entry) { // 可經過entry對象操做test.html文件 entry.file(function(file) { // FileReader文件系統中的讀取文件對象,用於獲取文件的內容 var fileReader = new plus.io.FileReader(); // alert("getFile:" + JSON.stringify(file)); // readAsDataURL: 以URL編碼格式讀取文件數據內容 fileReader.readAsDataURL(file, 'utf-8'); // onloadend: 文件讀取操做完成時的回調函數 fileReader.onloadend = function(evt) { console.log(evt.target.result); var b = dataURLtoBlob(evt.target.result); ws.send(b); // 發送blob數據 } // alert(file.size + '--' + file.name) }); }); }) function dataURLtoBlob(dataurl) { // 數據轉換爲Blob // 邏輯很複雜,這裏不解釋了。直接用就能夠了! var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); while(n--) { u8arr[n] = bstr.charCodeAt(n); } var a = new Blob([u8arr], { type: mime }); return a } </script> </html>
修改 message.html,渲染頁面。create_content多加一個參數
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <link rel="stylesheet" type="text/css" href="css/mui.css" /> </head> <body> <header class="mui-bar mui-bar-nav"> <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a> <h1 class="mui-title">個人好友</h1> </header> <div class="mui-content"> <ul class="mui-table-view" id="friend_list"> </ul> </div> </body> <script src="js/mui.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> mui.init() var Sdata = null; mui.back = function(){}; // 加載HTML5Puls mui.plusReady(function() { Sdata = plus.webview.currentWebview(); // post請求 mui.post( // 好友列表 window.serv + "/friend_list", {user_id:Sdata.user_id}, function(data){ console.log(JSON.stringify(data)); // 循環好友列表 for (var i = 0; i < data.data.length; i++) { // 執行自定義方法,渲染頁面 create_content(data.data[i],Sdata.msg_data); } } ) }); function create_content(content,msg_data){ // <li class="mui-table-view-cell mui-media"> // <a href="javascript:;"> // <img class="mui-media-object mui-pull-left" src="../images/shuijiao.jpg"> // <div class="mui-media-body"> // 幸福 // <p class='mui-ellipsis'>能和心愛的人一塊兒睡覺,是件幸福的事情;但是,打呼嚕怎麼辦?</p> // </div> // </a> // </li> // 角標 var spantag = document.createElement("span"); spantag.className = "mui-badge mui-badge-red"; // content是一個字典,要獲取friend_id。不能使用get,只能使用. // 若是獲取不到,值爲undefine spantag.innerText = msg_data[content.friend_id] var litag = document.createElement("li"); litag.className = "mui-table-view-cell mui-media"; var atag = document.createElement("a"); atag.id = content.friend_id; // 點擊事件 atag.onclick = function(){ console.log(this.id); open_chat(this.id); //執行自定義方法open_chat } var imgtag = document.createElement("img"); imgtag.className = "mui-media-object mui-pull-left"; imgtag.src = "avatar/" + content.friend_avatar; var divtag = document.createElement("div"); divtag.className = "mui-media-body"; divtag.innerText = content.friend_remark; var ptag = document.createElement("p"); ptag.className = "mui-ellipsis"; ptag.innerText = content.friend_name; litag.appendChild(atag); atag.appendChild(imgtag); atag.appendChild(divtag); atag.appendChild(spantag); divtag.appendChild(ptag); document.getElementById("friend_list").appendChild(litag); } function open_chat(friend_id){ // 打開chat.html mui.openWindow({ url:"chat.html", id:"chat.html", extras:{ // 傳參給chat.html friend_id:friend_id } }) } </script> </html>
使用模擬器從新訪問,效果以下:
發現了 undefined,這個不該該出現角標。這個是一個已知的bug,有興趣的人,能夠修改一下。
提示:使用css中的display:none
點擊 小甜甜,再返回頁面時,這裏的角標應該重置爲0。而且不顯示纔對。
修改 message.html,觸發點擊事件時,角標重置爲0
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <link rel="stylesheet" type="text/css" href="css/mui.css" /> </head> <body> <header class="mui-bar mui-bar-nav"> <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a> <h1 class="mui-title">個人好友</h1> </header> <div class="mui-content"> <ul class="mui-table-view" id="friend_list"> </ul> </div> </body> <script src="js/mui.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> mui.init() var Sdata = null; mui.back = function(){}; // 加載HTML5Puls mui.plusReady(function() { Sdata = plus.webview.currentWebview(); // post請求 mui.post( // 好友列表 window.serv + "/friend_list", {user_id:Sdata.user_id}, function(data){ console.log(JSON.stringify(data)); // 循環好友列表 for (var i = 0; i < data.data.length; i++) { // 執行自定義方法,渲染頁面 create_content(data.data[i],Sdata.msg_data); } } ) }); function create_content(content,msg_data){ // <li class="mui-table-view-cell mui-media"> // <a href="javascript:;"> // <img class="mui-media-object mui-pull-left" src="../images/shuijiao.jpg"> // <div class="mui-media-body"> // 幸福 // <p class='mui-ellipsis'>能和心愛的人一塊兒睡覺,是件幸福的事情;但是,打呼嚕怎麼辦?</p> // </div> // </a> // </li> // 角標 var spantag = document.createElement("span"); spantag.className = "mui-badge mui-badge-red"; // content是一個字典,要獲取friend_id。不能使用get,只能使用. // 若是獲取不到,值爲undefine spantag.innerText = msg_data[content.friend_id] var litag = document.createElement("li"); litag.className = "mui-table-view-cell mui-media"; var atag = document.createElement("a"); atag.id = content.friend_id; // 點擊事件 atag.onclick = function(){ console.log(this.id); spantag.innerText = 0; // 重置爲0 //執行自定義方法open_chat open_chat(this.id); } var imgtag = document.createElement("img"); imgtag.className = "mui-media-object mui-pull-left"; imgtag.src = "avatar/" + content.friend_avatar; var divtag = document.createElement("div"); divtag.className = "mui-media-body"; divtag.innerText = content.friend_remark; var ptag = document.createElement("p"); ptag.className = "mui-ellipsis"; ptag.innerText = content.friend_name; litag.appendChild(atag); atag.appendChild(imgtag); atag.appendChild(divtag); atag.appendChild(spantag); divtag.appendChild(ptag); document.getElementById("friend_list").appendChild(litag); } function open_chat(friend_id){ // 打開chat.html mui.openWindow({ url:"chat.html", id:"chat.html", extras:{ // 傳參給chat.html friend_id:friend_id } }) } </script> </html>
使用模擬器訪問,效果以下:
能夠發現,聊天列表返回時,已經重置爲0了。可是底部現象卡尚未變更!莫急,下面來處理它。
怎麼實現呢?因爲index.html頁面是母模,它只負責顯示底部選項卡。
在chat.html頁面給index.html,執行一個fire(開火)事件就能夠了!可是:一個頁面,只能fire一次。
因爲chat.html已經存在了一個fire事件。因此只能在message.html作fire
修改 message.html,增長fire
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <link rel="stylesheet" type="text/css" href="css/mui.css" /> </head> <body> <header class="mui-bar mui-bar-nav"> <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a> <h1 class="mui-title">個人好友</h1> </header> <div class="mui-content"> <ul class="mui-table-view" id="friend_list"> </ul> </div> </body> <script src="js/mui.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> mui.init() var Sdata = null; mui.back = function(){}; // 加載HTML5Puls mui.plusReady(function() { Sdata = plus.webview.currentWebview(); // post請求 mui.post( // 好友列表 window.serv + "/friend_list", {user_id:Sdata.user_id}, function(data){ console.log(JSON.stringify(data)); // 循環好友列表 for (var i = 0; i < data.data.length; i++) { // 執行自定義方法,渲染頁面 create_content(data.data[i],Sdata.msg_data); } } ) }); function create_content(content,msg_data){ // <li class="mui-table-view-cell mui-media"> // <a href="javascript:;"> // <img class="mui-media-object mui-pull-left" src="../images/shuijiao.jpg"> // <div class="mui-media-body"> // 幸福 // <p class='mui-ellipsis'>能和心愛的人一塊兒睡覺,是件幸福的事情;但是,打呼嚕怎麼辦?</p> // </div> // </a> // </li> // 角標 var spantag = document.createElement("span"); spantag.className = "mui-badge mui-badge-red"; // content是一個字典,要獲取friend_id。不能使用get,只能使用. // 若是獲取不到,值爲undefine spantag.innerText = msg_data[content.friend_id] var litag = document.createElement("li"); litag.className = "mui-table-view-cell mui-media"; var atag = document.createElement("a"); atag.id = content.friend_id; // 點擊事件 atag.onclick = function(){ // console.log(this.id); //執行自定義方法open_chat open_chat(this.id,spantag.innerText); spantag.innerText = 0; // 重置爲0 } var imgtag = document.createElement("img"); imgtag.className = "mui-media-object mui-pull-left"; imgtag.src = "avatar/" + content.friend_avatar; var divtag = document.createElement("div"); divtag.className = "mui-media-body"; divtag.innerText = content.friend_remark; var ptag = document.createElement("p"); ptag.className = "mui-ellipsis"; ptag.innerText = content.friend_name; litag.appendChild(atag); atag.appendChild(imgtag); atag.appendChild(divtag); atag.appendChild(spantag); divtag.appendChild(ptag); document.getElementById("friend_list").appendChild(litag); } function open_chat(friend_id,cut_count){ // 打開chat.html // 獲取index.html var index = plus.webview.getWebviewById("HBuilder") // 執行fire mui.fire(index,"cut_msg_count",{cut:cut_count}) mui.openWindow({ url:"chat.html", id:"chat.html", extras:{ // 傳參給chat.html friend_id:friend_id } }) } </script> </html>
修改 index.html,監聽 cut_msg_count事件
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <title></title> <script src="js/mui.js"></script> <link href="css/mui.min.css" rel="stylesheet" /> </head> <body> <!--底部選項卡--> <nav class="mui-bar mui-bar-tab"> <a class="mui-tab-item mui-active" id="index"> <span class="mui-icon mui-icon-home"></span> <span class="mui-tab-label">首頁</span> </a> <a class="mui-tab-item" id="message"> <span class="mui-icon mui-icon-chat"> <span class="mui-badge mui-badge-red" id="msg_count">0</span> </span> <span class="mui-tab-label">消息</span> </a> <a class="mui-tab-item"> <span class="mui-icon mui-icon-email"></span> <span class="mui-tab-label">郵件</span> </a> <a class="mui-tab-item" id="login"> <span class="mui-icon mui-icon-gear"></span> <span class="mui-tab-label">設置</span> </a> </nav> </body> <script type="text/javascript" charset="utf-8"> var ws = null; // websocket對象 var msg_data = null; // 消息數據 mui.init({ subpages: [{ url: "main.html", id: "main.html", styles: window.styles }] }); mui.plusReady(function() { // console.log(JSON.stringify(plus.webview.currentWebview())) if(plus.storage.getItem("user")) { // 判斷是否登陸 console.log('已結登陸了!'); //鏈接websocket鏈接 ws = new WebSocket("ws://" + window.ws_serv + "/app/" + plus.storage.getItem("user")) // 發送post請求 console.log(window.serv + "/get_msg_list"); mui.post( // 訪問消息列表 window.serv + "/get_msg_list", { user_id: plus.storage.getItem("user") }, function(data) { console.log(JSON.stringify(data)); // {"code":0,"data":{"5ba0f1f2e12532418089bf88":1,"count":1},"msg":""} msg_data = data.data; // 修改消息選項卡的角標數字 document.getElementById("msg_count").innerText = msg_data.count; } ); // 客戶端接收服務端數據時觸發 ws.onmessage = function() {}; } // 自動重連 ws.onclose = function() { window.location.reload(); } }); // 消息 document.getElementById("message").addEventListener("tap", function() { mui.openWindow({ url: "message.html", id: "message.html", styles: window.styles, extras: { // 傳輸用戶id,給message.html user_id: plus.storage.getItem("user"), msg_data: msg_data, // "data":{"5ba0f1f2e12532418089bf88":1,"count":1} } }) }); document.getElementById("index").addEventListener("tap", function() { mui.openWindow({ url: "main.html", id: "main.html", styles: window.styles }) }) document.getElementById("login").addEventListener("tap", function() { // 自動登陸,判斷storage中的user存在,就跳轉到user_info,不然跳轉login if(plus.storage.getItem("user")) { mui.openWindow({ url: "user_info.html", id: "user_info.html", styles: window.styles, extras: { user_id: plus.storage.getItem("user") } }) } else { mui.openWindow({ url: "login.html", id: "login.html", styles: window.styles }) } }) document.addEventListener("login", function(data) { // fire事件接收消息,使用data.detail // index是爲作顯示區分 mui.toast("index" + data.detail.msg) }); document.addEventListener("send_music", function(data) { //監聽send_music事件 var music_name = data.detail.music_name; //獲取player.html使用fire發送的music_name值 var toy_id = data.detail.toy_id; //獲取發送的玩具id send_str = { //構造數據 data: music_name, to_user: toy_id, // 目標用戶,這裏統一格式 msg_type: "music", // 類型爲音樂 } // 發送數據給後端,注意要json序列化 ws.send(JSON.stringify(send_str)); }); document.addEventListener("send_msg", function(data) { //發送消息 var filename = data.detail.filename var to_user = data.detail.to_user send_str = { to_user: to_user } ws.send(JSON.stringify(send_str)) plus.io.resolveLocalFileSystemURL(filename, function(entry) { // 可經過entry對象操做test.html文件 entry.file(function(file) { // FileReader文件系統中的讀取文件對象,用於獲取文件的內容 var fileReader = new plus.io.FileReader(); // alert("getFile:" + JSON.stringify(file)); // readAsDataURL: 以URL編碼格式讀取文件數據內容 fileReader.readAsDataURL(file, 'utf-8'); // onloadend: 文件讀取操做完成時的回調函數 fileReader.onloadend = function(evt) { console.log(evt.target.result); var b = dataURLtoBlob(evt.target.result); ws.send(b); // 發送blob數據 } // alert(file.size + '--' + file.name) }); }); }); // 監聽cut_msg_count事件,由message.html向index.html執行fire document.addEventListener("cut_msg_count", function(data) { var msg_count = document.getElementById("msg_count"); var cut = parseInt(data.detail.cut); // parseInt表示強制轉換 var count = parseInt(msg_count.innerText); // 默認獲取innerText是字符串,須要強制轉換 msg_count.innerText = count - cut; // 總數 減去 點擊聊天會話的數量,好比小甜甜的 }); function dataURLtoBlob(dataurl) { // 數據轉換爲Blob // 邏輯很複雜,這裏不解釋了。直接用就能夠了! var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); while(n--) { u8arr[n] = bstr.charCodeAt(n); } var a = new Blob([u8arr], { type: mime }); return a } </script> </html>
效果以下:
能夠發現,底部選項卡,也變成0了
那是由於後端redis的數據沒有更改。
進入flask項目,修改 utils-->chat_redis.py
from setting import REDIS_DB import json def save_msg(sender, to_user): # 保存消息 # 1.查詢一下xiao的Redis是否有數據 user_msg_redis = REDIS_DB.get(to_user) if user_msg_redis: # 2.將xiao的數據反序列化成字典 { sender : n } user_msg_dict = json.loads(user_msg_redis) # 3.判斷有沒有 sender 的用戶發來的消息數量 if user_msg_dict.get(sender): # 數量加1 user_msg_dict[sender] += 1 else: # 第一次,初始值爲1 user_msg_dict[sender] = 1 # 4.若是xiao是剛創建好的用戶,他是沒有消息的,字典是空 else: user_msg_dict = {sender: 1} # 5.序列化用戶消息字典user_msg_dict user_msg_redis = json.dumps(user_msg_dict) # 6.存回Redis REDIS_DB.set(to_user, user_msg_redis) def get_msg_list(user): # 獲取消息 user_msg_redis = REDIS_DB.get(user) if user_msg_redis: user_msg_dict = json.loads(user_msg_redis) # 統計數量 user_msg_dict["count"] = sum(user_msg_dict.values()) else: user_msg_dict = {"count":0} return user_msg_dict def get_user_msg_one(sender, to_user): # 獲取用戶一個好友消息 user_msg_redis = REDIS_DB.get(to_user) if user_msg_redis: user_msg_dict = json.loads(user_msg_redis) if user_msg_dict.get(sender): # return user_msg_dict.get(sender) user_msg_dict[sender] = 0 else: user_msg_dict = {sender:0} user_msg_redis = json.dumps(user_msg_dict) REDIS_DB.set(to_user,user_msg_redis) # 修改redis
修改 serv-->chat.py,增長 chat_redis.get_user_msg_one
from flask import Blueprint, request, jsonify from setting import MONGO_DB from setting import RET from bson import ObjectId from utils import chat_redis cht = Blueprint("cht", __name__) @cht.route("/chat_list", methods=["POST"]) def chat_list(): # 聊天記錄列表 user_id = request.form.get("user_id") friend_id = request.form.get("friend_id") print(friend_id) chat_window = MONGO_DB.chat.find_one({"user_list": {"$all": [user_id, friend_id]}}) fri = MONGO_DB.toys.find_one({"_id": ObjectId(friend_id)}) baby_name = fri.get("baby_name") cl = chat_window.get("chat_list") RET["code"] = 0 RET["msg"] = baby_name RET["data"] = cl # 獲取用戶單個好友記錄,修改redis的值 chat_redis.get_user_msg_one(friend_id,user_id) return jsonify(RET) @cht.route("/get_msg", methods=["POST"]) def get_msg(): # 獲取聊天語言文件 user_id = request.form.get("user_id") sender = request.form.get("sender") chat_window = MONGO_DB.chat.find_one({"user_list": {"$all": [user_id, sender]}}) new_msg = chat_window.get("chat_list")[-1] RET["code"] = 0 RET["msg"] = "" RET["data"] = new_msg.get("msg") return jsonify(RET) @cht.route("/get_msg_list", methods=["POST"]) def get_msg_list(): user_id = request.form.get("user_id") user_msg_dict = chat_redis.get_msg_list(user_id) RET["code"] = 0 RET["msg"] = "" RET["data"] = user_msg_dict return jsonify(RET)
打開夜神模擬器,重啓 裏面的HBuilder APP。再次點擊,查看redis
127.0.0.1:6379> get 5b9bb768e1253281608e96eb "{\"5ba0f1f2e12532418089bf88\": 0}"
發現已經更新爲0了
可是發消息,可能不止一條。若是有消息,角標的數字應該自動加。
進入 HBuilder項目MyApp,修改index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <title></title> <script src="js/mui.js"></script> <link href="css/mui.min.css" rel="stylesheet" /> </head> <body> <!--底部選項卡--> <nav class="mui-bar mui-bar-tab"> <a class="mui-tab-item mui-active" id="index"> <span class="mui-icon mui-icon-home"></span> <span class="mui-tab-label">首頁</span> </a> <a class="mui-tab-item" id="message"> <span class="mui-icon mui-icon-chat"> <span class="mui-badge mui-badge-red" id="msg_count">0</span> </span> <span class="mui-tab-label">消息</span> </a> <a class="mui-tab-item"> <span class="mui-icon mui-icon-email"></span> <span class="mui-tab-label">郵件</span> </a> <a class="mui-tab-item" id="login"> <span class="mui-icon mui-icon-gear"></span> <span class="mui-tab-label">設置</span> </a> </nav> </body> <script type="text/javascript" charset="utf-8"> var ws = null; // websocket對象 var msg_data = null; // 消息數據 mui.init({ subpages: [{ url: "main.html", id: "main.html", styles: window.styles }] }); mui.plusReady(function() { // console.log(JSON.stringify(plus.webview.currentWebview())) if(plus.storage.getItem("user")) { // 判斷是否登陸 console.log('已結登陸了!'); //鏈接websocket鏈接 ws = new WebSocket("ws://" + window.ws_serv + "/app/" + plus.storage.getItem("user")) // 發送post請求 console.log(window.serv + "/get_msg_list"); mui.post( // 訪問消息列表 window.serv + "/get_msg_list", { user_id: plus.storage.getItem("user") }, function(data) { console.log(JSON.stringify(data)); // {"code":0,"data":{"5ba0f1f2e12532418089bf88":1,"count":1},"msg":""} msg_data = data.data; // 修改消息選項卡的角標數字 document.getElementById("msg_count").innerText = msg_data.count; } ); // 客戶端接收服務端數據時觸發 ws.onmessage = function(data) { console.log(data.data); var msg = JSON.parse(data.data); var chat = plus.webview.getWebviewById("chat.html"); mui.fire(chat, "new_msg", { // 向chat.html傳值 data: msg }); var msg_count = document.getElementById("msg_count"); // 當前頁面加1 msg_count.innerText = parseInt(msg_count.innerText) + 1; // 加1,用於message.html顯示 msg_data[msg.from_user]++; }; } // 自動重連 ws.onclose = function() { window.location.reload(); } }); // 消息 document.getElementById("message").addEventListener("tap", function() { mui.openWindow({ url: "message.html", id: "message.html", styles: window.styles, extras: { // 傳輸用戶id,給message.html user_id: plus.storage.getItem("user"), msg_data: msg_data, // "data":{"5ba0f1f2e12532418089bf88":1,"count":1} } }) }); document.getElementById("index").addEventListener("tap", function() { mui.openWindow({ url: "main.html", id: "main.html", styles: window.styles }) }) document.getElementById("login").addEventListener("tap", function() { // 自動登陸,判斷storage中的user存在,就跳轉到user_info,不然跳轉login if(plus.storage.getItem("user")) { mui.openWindow({ url: "user_info.html", id: "user_info.html", styles: window.styles, extras: { user_id: plus.storage.getItem("user") } }) } else { mui.openWindow({ url: "login.html", id: "login.html", styles: window.styles }) } }) document.addEventListener("login", function(data) { // fire事件接收消息,使用data.detail // index是爲作顯示區分 mui.toast("index" + data.detail.msg) }); document.addEventListener("send_music", function(data) { //監聽send_music事件 var music_name = data.detail.music_name; //獲取player.html使用fire發送的music_name值 var toy_id = data.detail.toy_id; //獲取發送的玩具id send_str = { //構造數據 data: music_name, to_user: toy_id, // 目標用戶,這裏統一格式 msg_type: "music", // 類型爲音樂 } // 發送數據給後端,注意要json序列化 ws.send(JSON.stringify(send_str)); }); document.addEventListener("send_msg", function(data) { //發送消息 var filename = data.detail.filename var to_user = data.detail.to_user send_str = { to_user: to_user } ws.send(JSON.stringify(send_str)) plus.io.resolveLocalFileSystemURL(filename, function(entry) { // 可經過entry對象操做test.html文件 entry.file(function(file) { // FileReader文件系統中的讀取文件對象,用於獲取文件的內容 var fileReader = new plus.io.FileReader(); // alert("getFile:" + JSON.stringify(file)); // readAsDataURL: 以URL編碼格式讀取文件數據內容 fileReader.readAsDataURL(file, 'utf-8'); // onloadend: 文件讀取操做完成時的回調函數 fileReader.onloadend = function(evt) { console.log(evt.target.result); var b = dataURLtoBlob(evt.target.result); ws.send(b); // 發送blob數據 } // alert(file.size + '--' + file.name) }); }); }); // 監聽cut_msg_count事件,由message.html向index.html執行fire document.addEventListener("cut_msg_count", function(data) { var msg_count = document.getElementById("msg_count"); var cut = parseInt(data.detail.cut); // parseInt表示強制轉換 var count = parseInt(msg_count.innerText); // 默認獲取innerText是字符串,須要強制轉換 msg_count.innerText = count - cut; // 總數 減去 點擊聊天會話的數量,好比小甜甜的 }); function dataURLtoBlob(dataurl) { // 數據轉換爲Blob // 邏輯很複雜,這裏不解釋了。直接用就能夠了! var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); while(n--) { u8arr[n] = bstr.charCodeAt(n); } var a = new Blob([u8arr], { type: mime }); return a } </script> </html>
打開網頁,發送消息
發送2條消息
這裏會實時變化
這裏仍是0,是由於這個頁面的plusReady只會加載一次。這個是一個小bug
若是關閉進程,再次開啓,就會有了!
進入 flask後端,修改im_serv.py
from flask import Flask, request from geventwebsocket.websocket import WebSocket from geventwebsocket.handler import WebSocketHandler from gevent.pywsgi import WSGIServer import json, os from uuid import uuid4 from setting import AUDIO_FILE,CHAT_FILE from serv import content from utils import baidu_ai from utils import chat_redis import setting from bson import ObjectId import time app = Flask(__name__) user_socket_dict = {} # 空字典,用來存放用戶名和發送消息 @app.route("/toy/<tid>") def toy(tid): # 玩具鏈接 # 獲取請求的WebSocket對象 user_socket = request.environ.get("wsgi.websocket") # type:WebSocket if user_socket: # 設置鍵值對 user_socket_dict[tid] = user_socket print(user_socket_dict) # {'123456': <geventwebsocket.websocket.WebSocket object at 0x00000176ABD92E18>} file_name = "" to_user = "" # 循環,接收消息 while True: msg = user_socket.receive() if type(msg) == bytearray: file_name = f"{uuid4()}.wav" file_path = os.path.join(CHAT_FILE, file_name) with open(file_path, "wb") as f: f.write(msg) else: msg_dict = json.loads(msg) to_user = msg_dict.get("to_user") msg_type = msg_dict.get("msg_type") if to_user and file_name: other_user_socket = user_socket_dict.get(to_user) if msg_type == "ai": q = baidu_ai.audio2text(file_path) print(q) ret = baidu_ai.my_nlp(q, tid) other_user_socket.send(json.dumps(ret)) else: send_str = { "code": 0, "from_user": tid, "msg_type": "chat", "data": file_name } if other_user_socket: # 當websocket鏈接存在時 chat_redis.save_msg(tid, to_user) # 保存消息到redis # 發送數據 other_user_socket.send(json.dumps(send_str)) else: # 離線消息 chat_redis.save_msg(tid, to_user) # 保存聊天記錄到MongoDB _add_chat(tid, to_user, send_str.get("data")) to_user = "" file_name = "" @app.route("/app/<uid>") def user_app(uid): # 手機app鏈接 user_socket = request.environ.get("wsgi.websocket") # type:WebSocket if user_socket: user_socket_dict[uid] = user_socket # { uid : websocket} print(user_socket_dict) file_name = "" to_user = "" while True: # 手機聽歌 把歌曲發送給 玩具 1.將文件直接發送給玩具 2.將當前聽的歌曲名稱或ID發送到玩具 msg = user_socket.receive() if type(msg) == bytearray: # 判斷類型爲bytearray file_name = f"{uuid4()}.amr" # 文件後綴爲amr,安卓和ios通用 file_path = os.path.join(CHAT_FILE, file_name) # 存放在chat目錄 print(msg) with open(file_path, "wb") as f: f.write(msg) # 寫入文件 # 將amr轉換爲mp3,由於html中的audio不支持amr os.system(f"ffmpeg -i {file_path} {file_path}.mp3") else: msg_dict = json.loads(msg) to_user = msg_dict.get("to_user") # 獲取目標用戶 if msg_dict.get("msg_type") == "music": other_user_socket = user_socket_dict.get(to_user) send_str = { "code": 0, "from_user": uid, "msg_type": "music", "data": msg_dict.get("data") } other_user_socket.send(json.dumps(send_str)) # res = content._content_one(content_id) if file_name and to_user: # 若是文件名和發送用戶同上存在時 # 查詢玩具信息 res = setting.MONGO_DB.toys.find_one({"_id": ObjectId(to_user)}) # 獲取friend_remark fri = [i.get("friend_remark") for i in res.get("friend_list") if i.get("friend_id") == uid][0] msg_file_name = baidu_ai.text2audio(f"你有來自{fri}的消息") # 獲取websocket對象 other_user_socket = user_socket_dict.get(to_user) # 構造數據 send_str = { "code": 0, "from_user": uid, "msg_type": "chat", # 聊天類型 # 後綴必須是mp3的 "data": msg_file_name } if other_user_socket: chat_redis.save_msg(uid, to_user) # 發送數據給前端頁面 other_user_socket.send(json.dumps(send_str)) else: # 保存redis chat_redis.save_msg(uid, to_user) # 添加聊天記錄到數據庫 _add_chat(uid, to_user, f"{file_name}.mp3") # 最後必定要清空這2個變量,不然形成混亂 file_name = "" to_user = "" def _add_chat(sender, to_user, msg): # 添加聊天記錄到數據庫 chat_window = setting.MONGO_DB.chat.find_one({"user_list": {"$all": [sender, to_user]}}) if not chat_window.get("chat_list"): chat_window["chat_list"] = [{ "sender": sender, "msg": msg, "updated_at": time.time(), }] res = setting.MONGO_DB.chat.update_one({"_id": ObjectId(chat_window.get("_id"))}, {"$set": chat_window}) else: chat = { "sender": sender, "msg": msg, "updated_at": time.time(), } res = setting.MONGO_DB.chat.update_one({"_id": ObjectId(chat_window.get("_id"))}, {"$push": {"chat_list": chat}}) return res if __name__ == '__main__': # 建立一個WebSocket服務器 http_serv = WSGIServer(("0.0.0.0", 9528), app, handler_class=WebSocketHandler) # 開始監聽HTTP請求 http_serv.serve_forever() ''' { "code": 0, "from_user": uid, # APP用戶id "data": music_name # 歌曲名 } '''
給小甜甜發送消息,可能不止一條。後端收取消息,要有多條
修改 serv-->chat.py,改成[-count:] 。若是是2條,就是[-2:]。表示最後2條!
from flask import Blueprint, request, jsonify from setting import MONGO_DB from setting import RET from bson import ObjectId from utils import chat_redis cht = Blueprint("cht", __name__) @cht.route("/chat_list", methods=["POST"]) def chat_list(): # 聊天記錄列表 user_id = request.form.get("user_id") friend_id = request.form.get("friend_id") print(friend_id) chat_window = MONGO_DB.chat.find_one({"user_list": {"$all": [user_id, friend_id]}}) fri = MONGO_DB.toys.find_one({"_id": ObjectId(friend_id)}) baby_name = fri.get("baby_name") cl = chat_window.get("chat_list") RET["code"] = 0 RET["msg"] = baby_name RET["data"] = cl # 獲取用戶單個好友記錄,修改redis的值 chat_redis.get_user_msg_one(friend_id,user_id) return jsonify(RET) @cht.route("/get_msg", methods=["POST"]) def get_msg(): # 獲取聊天語言文件 user_id = request.form.get("user_id") sender = request.form.get("sender") count = 1 # 初始值 if not sender: msg_dict = chat_redis.get_msg_list(user_id) print(msg_dict,"msg_dict") sender = list(msg_dict.keys())[0] count = msg_dict[sender] else: # 獲取用戶某個好友的值 count = chat_redis.get_user_msg_one(sender,user_id) # $all 表示多個條件都成立時。這裏表示user_list字段中user_id和sender必須都存在才行! chat_window = MONGO_DB.chat.find_one({"user_list": {"$all": [user_id, sender]}}) # [-count:] 表示獲取最後的幾條消息。好比: -1: 表示最後一條 new_msg = chat_window.get("chat_list")[-count:] RET["code"] = 0 RET["msg"] = "" RET["data"] = new_msg # chat_redis.get_user_msg_one(sender,user_id) return jsonify(RET) @cht.route("/get_msg_list", methods=["POST"]) def get_msg_list(): user_id = request.form.get("user_id") user_msg_dict = chat_redis.get_msg_list(user_id) RET["code"] = 0 RET["msg"] = "" RET["data"] = user_msg_dict return jsonify(RET)
修改 utils-->chat_redis.py
from setting import REDIS_DB import json def save_msg(sender, to_user): # 保存消息 # 1.查詢一下xiao的Redis是否有數據 user_msg_redis = REDIS_DB.get(to_user) if user_msg_redis: # 2.將xiao的數據反序列化成字典 { sender : n } user_msg_dict = json.loads(user_msg_redis) # 3.判斷有沒有 sender 的用戶發來的消息數量 if user_msg_dict.get(sender): # 數量加1 user_msg_dict[sender] += 1 else: # 第一次,初始值爲1 user_msg_dict[sender] = 1 # 4.若是xiao是剛創建好的用戶,他是沒有消息的,字典是空 else: user_msg_dict = {sender: 1} # 5.序列化用戶消息字典user_msg_dict user_msg_redis = json.dumps(user_msg_dict) # 6.存回Redis REDIS_DB.set(to_user, user_msg_redis) def get_msg_list(user): # 獲取消息 user_msg_redis = REDIS_DB.get(user) if user_msg_redis: user_msg_dict = json.loads(user_msg_redis) # 統計數量 user_msg_dict["count"] = sum(user_msg_dict.values()) else: user_msg_dict = {"count":0} return user_msg_dict def get_user_msg_one(sender, to_user): # 獲取用戶一個好友消息 user_msg_redis = REDIS_DB.get(to_user) if user_msg_redis: user_msg_dict = json.loads(user_msg_redis) if user_msg_dict.get(sender): return user_msg_dict.get(sender) # user_msg_dict[sender] = 0 else: user_msg_dict = {sender:0} user_msg_redis = json.dumps(user_msg_dict) REDIS_DB.set(to_user,user_msg_redis) # 修改redis
修改index.html,使用player.onended。它會接收多條
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <audio src="" autoplay="autoplay" controls id="player"></audio> <br> <input type="text" id="device_id"/> <button onclick="start_toy()">玩具開機鍵</button> <br> <button onclick="start_reco()">開始廢話</button> <br> <button onclick="stop_reco()">發送語音</button> <br> <button onclick="start_reco()">錄製消息</button> <span id="to_user"></span> <br> <button onclick="send_reco()">發送語音消息</button> <br> <button onclick="recv_msg()">收取消息</button> </body> <script src="/static/recorder.js"></script> <script src="/static/jquery.min.js"></script> <script type="application/javascript"> var serv = "http://127.0.0.1:9527"; var ws_serv = "ws://127.0.0.1:9528"; // 獲取音頻文件 var get_music = serv + "/get_audio/"; var get_chat = serv + "/get_chat/"; var ws = null; // WebSocket 對象 var reco = null; // 建立AudioContext對象 var audio_context = new AudioContext(); var toy_id = null; //要獲取音頻和視頻 navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); // 拿到媒體對象,容許音頻對象 navigator.getUserMedia({audio: true}, create_stream, function (err) { console.log(err) }); //建立媒體流容器 function create_stream(user_media) { var stream_input = audio_context.createMediaStreamSource(user_media); // 給Recoder 建立一個空間,麥克風說的話,均可以錄入。是一個流 reco = new Recorder(stream_input); } function start_reco() { //開始錄音 reco.record(); //往裏面寫流 } function stop_reco() { //中止錄音 reco.stop(); //中止寫入流 get_audio(); //調用自定義方法 reco.clear(); //清空容器 } {#function get_audio() { // 獲取音頻#} {# reco.exportWAV(function (wav_file) {#} {# ws.send(wav_file); //使用websocket鏈接發送數據給後端#} {# })#} {# }#} function send_reco() { reco.stop(); send_audio(); reco.clear(); } function send_audio() { var to_user = document.getElementById("to_user").innerText; var send_str = { "to_user": to_user }; ws.send(JSON.stringify(send_str)); reco.exportWAV(function (wav_file) { ws.send(wav_file); }) } function get_audio() { var send_str = { "to_user": toy_id, "msg_type": "ai" }; ws.send(JSON.stringify(send_str)); reco.exportWAV(function (wav_file) { ws.send(wav_file); }) } function start_toy() { // 玩具開機 // 獲取輸入的設備id var device_id = document.getElementById("device_id").value; // 發送post請求 $.post( // 這裏的地址必須是127.0.0.1,不然會有跨域問題 "http://127.0.0.1:9527/device_toy_id", // 發送設備id {device_id: device_id}, function (data) { console.log(data); toy_id = data.data.toy_id; // 玩具id // 修改audio標籤的src屬性 document.getElementById("player").src = get_music + data.data.audio; if (toy_id) { // 判斷玩具id存在時 ws = new WebSocket(ws_serv + "/toy/" + toy_id); ws.onmessage = function (data) { // console.log(get_music + data.data); var content = JSON.parse(data.data); //反序列化數據 // 判斷消息類型 if (content.msg_type == "chat") { document.getElementById("player").src = get_chat + content.data; document.getElementById("to_user").innerText = content.from_user; console.log(content.from_user + "給你發送了一條消息"); } if (content.msg_type == "music") { document.getElementById("player").src = get_music + content.data; console.log(content.from_user + "給你點播了歌兒"); } }; ws.onclose = function () { window.location.reload(); } } }, "json" // 規定預期的服務器響應的數據類型爲json ); } function recv_msg() { var to_user = document.getElementById("to_user").innerText; var player = document.getElementById("player"); to_user = document.getElementById("to_user").innerText; $.post( serv + "/get_msg", {user_id: toy_id, sender: to_user}, function (data) { // shift() 方法用於把數組的第一個元素從其中刪除,並返回第一個元素的值 var msg = data.data.shift(); document.getElementById("to_user").innerText = msg.sender; player.src = get_chat + msg.msg; //修改audio標籤src屬性 // onended 事件在視頻/音頻(audio/video)播放結束時觸發 player.onended = function () { // 若是長度大於0,也就是有1條或者多條時 if(data.data.length > 0){ //修改audio標籤src屬性,有多條時,會輪詢觸發 player.src = get_chat + data.data.shift().msg; }else{ return null; } } }, "json" ) } </script> </html>
重啓 manager.py和im_serv.py
從新訪問網頁,讓玩具開機。連續錄製2個語音
再點擊收取消息,網頁會先播放一條,再緊着播放第二條!
APP發送語音後,頁面語音提示,你有來自 xx 的消息
修改 serv-->chat.py,修改get_msg視圖函數
from flask import Blueprint, request, jsonify from setting import MONGO_DB from setting import RET from bson import ObjectId from utils import chat_redis cht = Blueprint("cht", __name__) @cht.route("/chat_list", methods=["POST"]) def chat_list(): # 聊天記錄列表 user_id = request.form.get("user_id") friend_id = request.form.get("friend_id") print(friend_id) chat_window = MONGO_DB.chat.find_one({"user_list": {"$all": [user_id, friend_id]}}) fri = MONGO_DB.toys.find_one({"_id": ObjectId(friend_id)}) baby_name = fri.get("baby_name") cl = chat_window.get("chat_list") RET["code"] = 0 RET["msg"] = baby_name RET["data"] = cl # 獲取用戶單個好友記錄,修改redis的值 chat_redis.get_user_msg_one(friend_id,user_id) return jsonify(RET) @cht.route("/get_msg", methods=["POST"]) def get_msg(): # 獲取聊天語言文件 user_id = request.form.get("user_id") sender = request.form.get("sender") count = 1 # 初始值 if not sender: msg_dict = chat_redis.get_msg_list(user_id) # print(msg_dict,"msg_dict") # 未讀數量 sender = [i for i in msg_dict.keys() if msg_dict[i] != 0 and i != "count"] if sender: sender = sender[0] count = msg_dict[sender] else: pass # 沒有任何消息了,能夠調用合成語言,提示一下 # filename= baidu_ai.text2audio("") # new_msg = [{sender:"",msg:filename}] else: # 獲取用戶某個好友的值 count = chat_redis.get_user_msg_one(sender,user_id) # $all 表示多個條件都成立時。這裏表示user_list字段中user_id和sender必須都存在才行! chat_window = MONGO_DB.chat.find_one({"user_list": {"$all": [user_id, sender]}}) # [-count:] 表示獲取最後的幾條消息。好比: -1: 表示最後一條 new_msg = chat_window.get("chat_list")[-count:] # 這裏能夠提示,您收到來自xx的幾條消息 # filename= baidu_ai.text2audio("") # new_msg.insert(0,{ # "sender":sender, # "msg":filename # }) RET["code"] = 0 RET["msg"] = "" RET["data"] = new_msg chat_redis.get_user_msg_one(sender,user_id) return jsonify(RET) @cht.route("/get_msg_list", methods=["POST"]) def get_msg_list(): user_id = request.form.get("user_id") user_msg_dict = chat_redis.get_msg_list(user_id) RET["code"] = 0 RET["msg"] = "" RET["data"] = user_msg_dict return jsonify(RET)
重啓 manager.py和im_serv.py
使用App發送2條消息
玩具頁面會有語音提示,你有來自 小魚的消息
點擊收取消息。會自動播放2條語音!
今日總結:
1.向app推送消息 Redis 中存儲消息: to_user : { sender : 1 } 消息按鈕 未讀消息 在message點擊打開 chat_window的時候 發起一個fire(cut)事件給index頁面 index頁面 根據 cut 值進行刪減 角標的數字 chat_window 未讀消息 message頁面將未讀消息數字置空 = 0 chat_window 頁面 發起的 chat_list 請求,加入邏輯 將redis中的未讀消息置空 = 0 2.玩具端消息推送 消息列表 - 區分用戶 array.shift() 刪除array中的第一個元素並返回 批量收取一個用戶的消息 AudioContext.onended = function(){ AudioContext.src = "music.mp3" } 批量收取消息的邏輯: 1.從sender未讀消息中拿出未讀數量 未讀數量不是 0 2.從chat_list中拿出最後的幾條未讀消息
完整代碼,參考github:
https://github.com/987334176/Intelligent_toy/archive/v1.5.zip