python 全棧開發,Day131(向app推送消息,玩具端消息推送)

先下載github代碼,下面的操做,都是基於這個版原本的!javascript

https://github.com/987334176/Intelligent_toy/archive/v1.4.zipphp

注意:因爲涉及到版權問題,此附件沒有圖片和音樂。請參考連接,手動採集一下!css

請參考連接:html

http://www.javashuo.com/article/p-splctlzd-t.html前端

 

1、向app推送消息

redis安裝

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
}
View Code

 

進入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))
View Code

 

看下面的示例圖:

 

修改 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)
View Code

 

修改 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  # 歌曲名
}
'''
View Code

重啓  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>
View Code

 

進入 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)
View Code

重啓 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>
View Code

 

修改 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>
View Code

 

使用模擬器從新訪問,效果以下:

 發現了 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>
View Code

 

使用模擬器訪問,效果以下:

能夠發現,聊天列表返回時,已經重置爲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>
View Code

 

修改 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>
View Code

 

 效果以下:

能夠發現,底部選項卡,也變成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
View Code

 

修改 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)
View Code

 

打開夜神模擬器,重啓 裏面的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>
View Code

 

打開網頁,發送消息

發送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  # 歌曲名
}
'''
View Code

 

給小甜甜發送消息,可能不止一條。後端收取消息,要有多條

 

修改 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)
View Code

 

 修改 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
View Code

 

修改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>
View Code

重啓 manager.py和im_serv.py

 

從新訪問網頁,讓玩具開機。連續錄製2個語音

再點擊收取消息,網頁會先播放一條,再緊着播放第二條!

 

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)
View Code

重啓 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中拿出最後的幾條未讀消息
View Code

 

完整代碼,參考github:

https://github.com/987334176/Intelligent_toy/archive/v1.5.zip

相關文章
相關標籤/搜索