最细节的Flask+echarts+mysql+自动刷新升级版(websocket)

前言

本人于年初基于Flask+echarts+mysql+ajax实现了自动刷新,但由于ajax轮询的弊端(请求必须由客户端向服务端发起,然后服务端进行响应),想看ajax实现的朋友可以看我写的这篇Flask+echarts+mysql+自动刷新。现改用双向传输、推送消息方面能够做到灵活、简便、高效实现方案,即数据库收到数据立刻向客户端发送数据,无需客户端先向数据库发送请求。

一、环境准备

网上已经有许多教程,但由于websocket版本匹配和引入库版本等问题,大部分截至我发文阶段都无法直接实现,经过本人测试更改,以下为实现代码,另附上我的相关版本,以免读者走弯路,如需要查看其他版本的匹配方案,见官方文档
以下为官方包版本匹配说明:
在这里插入图片描述
以下为本人操作案例版本:
在这里插入图片描述

二、Flask+websocket

服务端app.py:

from flask import Flask, render_template
from flask_socketio import SocketIO, emit
from threading import Lock
import random

async_mode = None
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
thread = None
thread_lock = Lock()


@app.route('/')
def index():
    return render_template('test.html')


@socketio.on('connect', namespace='/test_conn')
def test_connect():
    global thread
    with thread_lock:
        if thread is None:
            thread = socketio.start_background_task(target=background_thread)


def background_thread():
    while True:
        socketio.sleep(5)
        t = random.randint(1, 100)
        socketio.emit('server_response',
                      {
    
    'data': t}, namespace='/test_conn')


if __name__ == '__main__':
    socketio.run(app, debug=True)

客户端test.html:
注意以下引入版本,如果报错基本都是版本不匹配的问题。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <script type="text/javascript" src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
    <script type="text/javascript" src="//cdn.bootcss.com/socket.io/1.5.1/socket.io.min.js"></script>
</head>
<body>
<h2 id="t"></h2>
<script type="text/javascript">
    $(document).ready(function() {
      
      
        namespace = '/test_conn';
        var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
        socket.on('server_response', function(res) {
      
      
            console.log(res.data)
            var t = res.data;
            $("#t").text(t);
        });

    });
</script>
</body>
</html>

实现结果:
请添加图片描述
附上参考文献

三、Flask+echarts+websocket

服务端:

# encoding:utf-8
# !/usr/bin/env python
import psutil
import time
from threading import Lock
from flask import Flask, render_template
from flask_socketio import SocketIO

async_mode = None
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, async_mode=async_mode)
thread = None
thread_lock = Lock()


# 后台线程 产生数据,即刻推送至前端
def background_thread():
    count = 0
    while True:
        socketio.sleep(5)
        count += 1
        t = time.strftime('%M:%S', time.localtime())
        # 获取系统时间(只取分:秒)
        cpus = psutil.cpu_percent(interval=None, percpu=True)
        # 获取系统cpu使用率 non-blocking
        socketio.emit('server_response',
                      {
    
    'data': [t, cpus], 'count': count},
                      namespace='/test')
        # 注意:这里不需要客户端连接的上下文,默认 broadcast = True


@app.route('/')
def index():
    return render_template('test.html', async_mode=socketio.async_mode)


@socketio.on('connect', namespace='/test')
def test_connect():
    global thread
    with thread_lock:
        if thread is None:
            thread = socketio.start_background_task(target=background_thread)


if __name__ == '__main__':
    socketio.run(app, debug=True)

客户端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <script type="text/javascript" src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
    <script type="text/javascript" src="//cdn.bootcss.com/socket.io/1.5.1/socket.io.min.js"></script>
    <!-- ECharts  引入 -->
    <script src="https://cdn.staticfile.org/echarts/4.3.0/echarts.min.js"></script>
</head>
<body>
<div id="main" style="height:500px;border:1px solid #ccc;padding:10px;"></div>

    <script type="text/javascript">

    var myChart = echarts.init(document.getElementById('main'));

    myChart.setOption({
      
      
        title: {
      
      
            text: '系统监控走势图'
        },
        tooltip: {
      
      },
        legend: {
      
      
            data:['cpu']
        },
        xAxis: {
      
      
            data: []
        },
        yAxis: {
      
      },
        series: [{
      
      
            name: 'cpu',
            type: 'line',
            data: []
        }]
    });


    var time = ["","","","","","","","","",""],
        cpu = [0,0,0,0,0,0,0,0,0,0]


    //准备好统一的 callback 函数
    var update_mychart = function (res) {
      
      
    //res是json格式的response对象

        // 隐藏加载动画
        myChart.hideLoading();

        // 准备数据
        time.push(res.data[0]);
        cpu.push(parseFloat(res.data[1]));
        if (time.length >= 10){
      
      
            time.shift();
            cpu.shift();
        }

        // 填入数据
        myChart.setOption({
      
      
            xAxis: {
      
      
                data: time
            },
            series: [{
      
      
                name: 'cpu', // 根据名字对应到相应的系列
                data: cpu
            }]
        });

    };

    // 首次显示加载动画
    myChart.showLoading();


    // 建立socket连接,等待服务器“推送”数据,用回调函数更新图表
    $(document).ready(function() {
      
      
        namespace = '/test';
        var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);

        socket.on('server_response', function(res) {
      
      
            update_mychart(res);
        });

    });

    </script>

</body>
</html>

效果:
请添加图片描述
参考文献

四、添加数据库测试

思路和第三节差不多,写一个线程不断往数据库中插入数据,客户端显示横轴为数据库插入时间,纵轴为相应数据。
本人参考这几篇文章Flask 操作Mysql数据库Flask-SQLAlchemy详解查询结果集转json

  1. 创建数据库
    这一步可以直接从数据库可视化软件navicate、mysql命令行或者是flask框架的拓展包来进行,为了方便后续操作,在这里使用flask框架的数据库拓展包Flask-SQLAlchemy,创建测试类数据库代码如下:

    db_create.py

    扫描二维码关注公众号,回复: 14531961 查看本文章
    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    import pymysql
    
    pymysql.install_as_MySQLdb()
    
    app = Flask(__name__)
    
    
    class Config(object):
        """配置参数"""
        # 设置连接数据库的URL
        user = 'root'
        password = 自己数据库的密码
        database = 'test'
        SQLALCHEMY_DATABASE_URI = 'mysql://%s:%[email protected]:3306/%s' % (user, password, database)
    
        # 设置sqlalchemy自动更跟踪数据库
        SQLALCHEMY_TRACK_MODIFICATIONS = True
    
        # 查询时会显示原始SQL语句
        SQLALCHEMY_ECHO = True
    
        # 禁止自动提交数据处理
        SQLALCHEMY_COMMIT_ON_TEARDOWN = False
    
    
    # 读取配置
    app.config.from_object(Config)
    
    # 创建数据库sqlalchemy工具对象
    db = SQLAlchemy(app)
    
    
    class Test(db.Model):
        # 定义表名
        __tablename__ = 'sea_data'
        # 定义字段
        id = db.Column(db.Integer, primary_key=True, autoincrement=True)  # id 主键、自增
        record_t = db.Column(db.DateTime, unique=True)  # record_t 上传时间
        temperature = db.Column(db.Float)  # 气温数值
    
    
    if __name__ == '__main__':
        # 删除所有表
        db.drop_all()
        # 创建所有表
        db.create_all()
    

    执行该脚本python db_create.py,看到表创建完成
    在这里插入图片描述

  2. 定时插入数据
    参考多线程定时器,以下为对db_create.py的补充:

    def insert():
        print("定时器启动了")
        print(threading.current_thread())  # 查看当前线程
        record_t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        temperature = round(random.uniform(0, 40), 2)  # 产生一个0-40之间的数字,保留两位小数
        print('hello')
        ins = Test(record_t=record_t, temperature=temperature)
        db.session.add(ins)
        db.session.commit()
        print('插入成功!')
        timer = threading.Timer(5, insert)  # 在run函数结束之前我再开启一个定时器
        timer.start()
    if __name__ == '__main__':
        # 删除所有表
        db.drop_all()
        # 创建所有表
        db.create_all()
        t1 = threading.Timer(5, function=insert)  # 过5s之后我执行后面的一个函数,开启一个线程
        t1.start()
    
        # 设置一个多线程
        # while True:
        #     time.sleep(10)  # 延时10s
        #     print('主线程')
    

    运行python db_create.py结果,5秒插入一次随机数据:
    在这里插入图片描述

  3. 查询数据
    想到两种方式,一种是通过查询数据库取最近十条记录传给前端渲染显示;另一种是只查询最近的一条,通过前端将最前一条数据挤出去显示。
    在这里我们使用第二种方法,就查询最近1条显示,前端会不断地将以往的数据挤出去并填充。

    def query():
        # 查询最近一条数据
        # 只有最后加.all()才能读到实例,order_by和limit是条件查询
        new = db.session.query(Test).order_by(Test.id.desc()).limit(1).all()
        print(new)
        print(
            class_to_dict(new))  # [{'temperature': 23.18, 'id': 5, 'record_t': datetime.datetime(2022, 10, 8, 10, 41, 35)}]
    
    
    # 查询结果转为字典
    def class_to_dict(obj):
        is_list = obj.__class__ == [].__class__
        is_set = obj.__class__ == set().__class__
        if is_list or is_set:
            obj_arr = []
            for o in obj:
                dict = {
          
          }
                a = o.__dict__
                if "_sa_instance_state" in a:
                    del a['_sa_instance_state']
                dict.update(a)
                obj_arr.append(dict)
            return obj_arr
        else:
            dict = {
          
          }
            a = obj.__dict__
            if "_sa_instance_state" in a:
                del a['_sa_instance_state']
            dict.update(a)
            return dict
    
  4. websocket查询数据
    在从数据库获取最新数据的时候出现了一个问题,插入的数据无法被获取,收到的数据都很旧,经过查询资料,这篇文章SQLAlchemy为了加快查速度,因而存在"缓存"机制解释了为什么,需要先清空缓存,再查询:

    #清空缓存
        db_session.commit()
    

    在这里实现源码,注释我都写在源码里了,在这里不过多做赘述,需要的朋友直接copy,这是demo结构:
    在这里插入图片描述

    后端 app_dbchart.py:

    # encoding:utf-8
    # !/usr/bin/env python
    import psutil
    import time
    from threading import Lock
    from flask import Flask, render_template
    from flask_socketio import SocketIO
    import threading
    
    from db_create import insert, query
    
    async_mode = None
    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret!'
    socketio = SocketIO(app, async_mode=async_mode)
    thread = None
    thread_lock = Lock()
    
    
    # 后台线程 产生数据,即刻推送至前端
    def background_thread():
        count = 0
    
        while True:
            socketio.sleep(5)
            count += 1
            #  目前问题,数据库无法读取到最新数据 query方法
            print(query())
            temperature = query()['temperature']
            record_t = query()['record_t']
            # t = time.strftime('%Y%M:%S', time.localtime())
            # # 获取系统时间(只取分:秒)
            # cpus = psutil.cpu_percent(interval=None, percpu=True)
            # # 获取系统cpu使用率 non-blocking
            socketio.emit('server_response',
                          {
          
          'data': [record_t, temperature], 'count': count},
                          namespace='/test')
            # 注意:这里不需要客户端连接的上下文,默认 broadcast = True
    
    
    @app.route('/')
    def index():
        return render_template('test_dbchart.html', async_mode=socketio.async_mode)
    
    
    @socketio.on('connect', namespace='/test')
    def test_connect():
        global thread
        with thread_lock:
            if thread is None:
                thread = socketio.start_background_task(target=background_thread)
    
    
    if __name__ == '__main__':
        # 定时插入后来个定时画图
        socketio.run(app, debug=True)
    
    

    db_create.py:

    import json
    import random
    import time
    
    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    import pymysql
    import datetime
    import threading
    
    pymysql.install_as_MySQLdb()
    
    app = Flask(__name__)
    
    class Config(object):
    	"""配置参数"""
    	# 设置连接数据库的URL
    	user = 'root'
    	password = 自己的密码
    	database = 'test'
    	SQLALCHEMY_DATABASE_URI = 'mysql://%s:%[email protected]:3306/%s' % (user, password, database)
    	# 设置sqlalchemy自动更跟踪数据库
    	SQLALCHEMY_TRACK_MODIFICATIONS = True
    	# 查询时会显示原始SQL语句
    	SQLALCHEMY_ECHO = True
    	# 禁止自动提交数据处理
    	SQLALCHEMY_COMMIT_ON_TEARDOWN = False
    	ENV = 'development'
    	DEBUG = True
    
    # 读取配置
    app.config.from_object(Config)
    
    # 创建数据库sqlalchemy工具对象
    db = SQLAlchemy(app)
    
    
    class Test(db.Model):
        # 定义表名
        __tablename__ = 'sea_data'
        # 定义字段
        id = db.Column(db.Integer, primary_key=True, autoincrement=True)  # id 主键、自增
        record_t = db.Column(db.DateTime, unique=True)  # record_t 上传时间
        temperature = db.Column(db.Float)  # 气温数值
    
    
    def insert():
        print("定时器启动了")
        print(threading.current_thread())  # 查看当前线程
        record_t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        temperature = round(random.uniform(0, 40), 2)  # 产生一个0-40之间的数字,保留两位小数
        ins = Test(record_t=record_t, temperature=temperature)
        db.session.add(ins)
        db.session.commit()
        print('插入成功!')
        timer = threading.Timer(5, insert)  # 在insert函数结束之前我再开启一个定时器
        timer.start()
    
    
    def create():
        # 创建所有表
        db.create_all()
    
    
    def drop():
        # 删除所有表
        db.drop_all()
    
    
    def query():
        # 清空缓存
        db.session.commit()
        # 查询最近一条数据
        # 只有最后加.all()才能读到实例,order_by和limit是条件查询
        new = db.session.query(Test).order_by(Test.id.desc()).limit(1).all()
        # [{'temperature': 23.18, 'id': 5, 'record_t': datetime.datetime(2022, 10, 8, 10, 41, 35)}]  list
        result = class_to_dict(new)
        # 取的时间json.dumps无法对字典中的datetime时间格式数据进行转化。因此需要添加特殊日期格式转化
        result[0]['record_t'] = json.dumps(result[0]['record_t'], cls=DateEncoder)
        # print(result[0])  # {'temperature': 23.18, 'id': 5, 'record_t': '"2022-10-08 10:41:35"'}
        return result[0]  # {'temperature': 23.18, 'id': 5, 'record_t': '"2022-10-08 10:41:35"'}
        # timer = threading.Timer(5, query)  # 在insert函数结束之前我再开启一个定时器
        # timer.start()
        # tem = result[0]['temperature']  # 23.18
        # return result[0]  # 应当返回这个字典,再按需取值
    
    
    # 查询结果转为字典
    def class_to_dict(obj):
        is_list = obj.__class__ == [].__class__
        is_set = obj.__class__ == set().__class__
        if is_list or is_set:
            obj_arr = []
            for o in obj:
                dict = {
          
          }
                a = o.__dict__
                if "_sa_instance_state" in a:
                    del a['_sa_instance_state']
                dict.update(a)
                obj_arr.append(dict)
            return obj_arr
        else:
            dict = {
          
          }
            a = obj.__dict__
            if "_sa_instance_state" in a:
                del a['_sa_instance_state']
            dict.update(a)
            return dict
    
    
    # 将json时间格式化
    class DateEncoder(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj, datetime.datetime):
                return obj.strftime("%Y-%m-%d %H:%M:%S")
            else:
                return json.JSONEncoder.default(self, obj)
    
    
    if __name__ == '__main__':
        # print(query())
        # 创建一个定时器,在程序运行在之后我开启一个insert函数
        t1 = threading.Timer(5, function=insert)  # 第一个参数是时间,例:过5s之后我执行后面的一个函数,开启一个线程
        t1.start()
        # print(query())
        # t2 = threading.Timer(5, function=query)  # 第一个参数是时间,例:过5s之后我执行后面的一个函数,开启一个线程
        # t2.start()
    
        # # 设置一个多线程
        # while True:
        #     time.sleep(10)  # 延时10s
        #     print('主线程')
    
    

    前端test_dbchart.html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script type="text/javascript" src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
        <script type="text/javascript" src="//cdn.bootcss.com/socket.io/1.5.1/socket.io.min.js"></script>
        <!-- ECharts  引入 -->
        <script src="https://cdn.staticfile.org/echarts/4.3.0/echarts.min.js"></script>
    </head>
    <body>
    <div id="main" style="height:500px;border:1px solid #ccc;padding:10px;"></div>
    
        <script type="text/javascript">
    
        var myChart = echarts.init(document.getElementById('main'));
    
        myChart.setOption({
            
            
            title: {
            
            
                text: '系统监控走势图'
            },
            tooltip: {
            
            },
            legend: {
            
            
                data:['temperature']
            },
            xAxis: {
            
            
                data: []
            },
            yAxis: {
            
            },
            series: [{
            
            
                name: 'temperature',
                type: 'line',
                data: []
            }]
        });
    
    
        var record_t = ["","","","","","","","","",""],
            temperature = [0,0,0,0,0,0,0,0,0,0]
    
    
        //准备好统一的 callback 函数
        var update_mychart = function (res) {
            
            
        //res是json格式的response对象
    
            // 隐藏加载动画
            myChart.hideLoading();
    
            // 准备数据
            record_t.push(res.data[0]);
            temperature.push(parseFloat(res.data[1]));
            console.log(temperature)
            if (record_t.length >= 10){
            
            
                record_t.shift();
                temperature.shift();
            }
    
            // 填入数据
            myChart.setOption({
            
            
                xAxis: {
            
            
                    data: record_t
                },
                series: [{
            
            
                    name: 'temperature', // 根据名字对应到相应的系列
                    data: temperature
                }]
            });
    
        };
    
        // 首次显示加载动画
        myChart.showLoading();
    
    
        // 建立socket连接,等待服务器“推送”数据,用回调函数更新图表
        $(document).ready(function() {
            
            
            namespace = '/test';
            var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
    
            socket.on('server_response', function(res) {
            
            
                update_mychart(res);
            });
    
        });
    
        </script>
    
    </body>
    </html>
    

    后端运行不分顺序,实现如视频:

    websocket+flask+mysql数据实时传输可视化

结语

websocket相对于ajax在实时监控等场景可以较好的应用,相比较于我之前写的ajax数据传输时延大大降低,后续两个工作:
1、物联网接收数据至服务器(数据库)后,自动向客户端发送数据形成成监控和统计图表。
2、进一步写前后端分离的vue+flask+websocket实现。

如果对您有帮助的话,点个赞呗,欢迎各位老爷打赏!

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44165950/article/details/127175293
今日推荐