开发 | 课用实时反馈系统

前情提要

Max上课提到要做一个实时反馈系统,用于看大家的进度。于是请教GPT4o做了一个很简单的反馈系统。

介绍

该系统由一个web客户端前端,一个web服务端前端,和一个python构建的服务端后端构成。用户通过点击yes或no来反馈自己是否跟上进度,且可以在右上角看到大家的进度。老师(服务端)则可以看到当前的统计,另外还可以清除一段时间后的统计结果。

部署

部署很简单,首先准备一台Linux服务器(或任何可以运行python的环境)。使用下列命令先更新包、安装Python3 和venv模块以及Redis服务器,用于处理高并发的计数存储,并启用Redis服务器。

sudo apt-get update
sudo apt-get upgrade -y

sudo apt-get install python3 python3-venv python3-pip -y

sudo apt-get install redis-server -y

sudo systemctl start redis-server
sudo systemctl enable redis-server

新建项目运行的目录,并启用venv。

cd ~
mkdir /name/your/directory
cd /name/your/directory
python3 -m venv venv
source venv/bin/activate

使用pip install安装必要的包。

pip install flask flask-cors redis

确保你的项目目录结构如下:

your-folder/
    ├── venv/
    ├── static/
    │   ├── client.html
    │   ├── server.html
    ├── server.py

创建 Systemd 服务文件(可选,但推荐)。为了确保你的Flask应用在系统重启后自动启动,创建一个systemd服务文件。

sudo nano /etc/systemd/system/[myapp].service

写入如下内容

[Unit]
Description=Gunicorn instance to serve myapp
After=network.target

[Service]
User=your_user_name
Group=www-data
WorkingDirectory=/home/your_user_name/name/your/directory
Environment="PATH=/home/your_user_name/name/your/directory/venv/bin"
ExecStart=/home/your_user_name/name/your/directory/venv/bin/gunicorn --workers 4 --bind 0.0.0.0:6999 server:app

[Install]
WantedBy=multi-user.target

启动该systemd

sudo systemctl start myapp
sudo systemctl enable myapp
sudo systemctl status myapp

完整的项目代码

server.py

from flask import Flask, request, jsonify
from flask_cors import CORS
import logging

app = Flask(__name__)
CORS(app)
logging.basicConfig(level=logging.DEBUG)

feedback_data = {
    "yes": 0,
    "no": 0
}

@app.route('/api/feedback', methods=['POST'])
def receive_feedback():
    global feedback_data
    data = request.json
    app.logger.debug(f"Received feedback: {data}")
    if data['feedback'] == 'yes':
        feedback_data['yes'] += 1
    elif data['feedback'] == 'no':
        feedback_data['no'] += 1
    return '', 204

@app.route('/api/status', methods=['GET'])
def get_status():
    total = feedback_data['yes'] + feedback_data['no']
    if total == 0:
        return jsonify({"yes": 0, "no": 0})
    yes_percentage = (feedback_data['yes'] / total) * 100
    no_percentage = (feedback_data['no'] / total) * 100
    app.logger.debug(f"Returning status: Yes: {yes_percentage}%, No: {no_percentage}%")
    return jsonify({"yes": yes_percentage, "no": no_percentage})

@app.route('/api/reset', methods=['POST'])
def reset_feedback():
    global feedback_data
    feedback_data = {"yes": 0, "no": 0}
    app.logger.debug("Feedback reset")
    return '', 204

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

client.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Client</title>
    <style>
        #feedback {
            position: fixed;
            top: 10px;
            right: 10px;
            background: white;
            border: 1px solid #ccc;
            padding: 10px;
            cursor: move;
        }
        button {
            width: 100px;
            height: 50px;
            margin: 20px;
            font-size: 1.5em;
        }
        .yes {
            background-color: green;
            color: white;
        }
        .no {
            background-color: red;
            color: white;
        }
    </style>
</head>
<body>
    <button class="yes" onclick="sendFeedback('yes')">Yes</button>
    <button class="no" onclick="sendFeedback('no')">No</button>

    <div id="feedback">Loading...</div>

    <script>
        function sendFeedback(value) {
            console.log(`Sending feedback: ${value}`);
            fetch('/api/feedback', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ feedback: value }),
            }).then(response => {
                console.log(`Feedback sent: ${value}`);
            }).catch(error => {
                console.error('Error sending feedback:', error);
            });
        }

        function updateFeedback() {
            fetch('/api/status')
                .then(response => response.json())
                .then(data => {
                    console.log(`Received feedback data: Yes: ${data.yes.toFixed(2)}%, No: ${data.no.toFixed(2)}%`);
                    document.getElementById('feedback').innerText = `Yes: ${data.yes.toFixed(2)}%, No: ${data.no.toFixed(2)}%`;
                })
                .catch(error => {
                    console.error('Error fetching status:', error);
                });
        }

        setInterval(updateFeedback, 1000);

        // Make the feedback window draggable
        let feedback = document.getElementById('feedback');
        feedback.onmousedown = function(event) {
            let shiftX = event.clientX - feedback.getBoundingClientRect().left;
            let shiftY = event.clientY - feedback.getBoundingClientRect().top;

            document.body.append(feedback);

            moveAt(event.pageX, event.pageY);

            function moveAt(pageX, pageY) {
                feedback.style.left = pageX - shiftX + 'px';
                feedback.style.top = pageY - shiftY + 'px';
            }

            function onMouseMove(event) {
                moveAt(event.pageX, event.pageY);
            }

            document.addEventListener('mousemove', onMouseMove);

            feedback.onmouseup = function() {
                document.removeEventListener('mousemove', onMouseMove);
                feedback.onmouseup = null;
            };
        };

        feedback.ondragstart = function() {
            return false;
        };
    </script>
</body>
</html>

server.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Server</title>
</head>
<body>
    <h1>Current Feedback</h1>
    <div id="stats">Loading...</div>
    <button onclick="resetFeedback()">Reset</button>

    <script>
        function updateStats() {
            console.log('Fetching status...');
            fetch('/api/status')
                .then(response => response.json())
                .then(data => {
                    console.log(`Status received: Yes: ${data.yes.toFixed(2)}%, No: ${data.no.toFixed(2)}%`);
                    document.getElementById('stats').innerText = `Yes: ${data.yes.toFixed(2)}%, No: ${data.no.toFixed(2)}%`;
                })
                .catch(error => {
                    console.error('Error fetching status:', error);
                });
        }

        function resetFeedback() {
            console.log('Resetting feedback...');
            fetch('/api/reset', { method: 'POST' }).then(() => {
                console.log('Feedback reset');
            }).catch(error => {
                console.error('Error resetting feedback:', error);
            });
        }

        setInterval(updateStats, 1000);
    </script>
</body>
</html>
滚动至顶部