python 全栈开发,Day131(向app推送消息,玩具端消息推送)

先下载github代码,下面的操作,都是基于这个版本来的!

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

注意:由于涉及到版权问题,此附件没有图片和音乐。请参考链接,手动采集一下!

请参考链接:

https://www.cnblogs.com/xiao987334176/p/9647993.html#autoid-3-4-0

 

一、向app推送消息

redis安装

Redis项目并没有正式支持Windows。然而,微软OpenTeaGeo集团开发并维护了以WIN64为目标的Windows端口。链接如下:

https://github.com/MicrosoftArchive/redis/releases

 

目前最新版本是3.2

 

完整下载链接如下:

https://github.com/MicrosoftArchive/redis/releases/download/win-3.2.100/Redis-x64-3.2.100.msi

 

开始安装

 注意:要勾选添加到系统环境变量

 默认端口是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个语音

再点击收取消息,网页会先播放一条,再紧着播放第二条!

 

二、玩具端消息推送

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

 

posted @ 2018-09-21 15:25  肖祥  阅读(567)  评论(0编辑  收藏  举报