ℹ 注意
当前页面刚刚完成,并没有经过多次检验。部分代码由GPT4o生成,请谨慎使用代码。
前情提要
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>