Python实例题:Flask实现简单聊天室
目录
Python实例题
题目
代码实现
app.py
index.html
实现原理
后端技术:
前端技术:
通信机制:
关键代码解析
1. 后端消息处理
2. 用户连接管理
3. 前端消息处理
4. 前端发送消息
使用说明
安装依赖:
运行服务器:
访问聊天室:
聊天功能:
扩展建议
增强功能:
性能优化:
用户界面:
安全增强:
Python实例题
题目
Flask实现简单聊天室
代码实现
app.py
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
import random
import stringapp = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
socketio = SocketIO(app, cors_allowed_origins="*")# 存储在线用户
online_users = {}# 首页路由
@app.route('/')
def index():return render_template('index.html')# 用户加入
@socketio.on('join')
def handle_join(data):user_id = data['userId']username = data['username']# 存储用户信息online_users[user_id] = {'username': username,'sid': request.sid}# 广播用户加入消息emit('message', {'userId': 'system','username': '系统','message': f'{username} 加入了聊天室'}, broadcast=True)# 更新在线用户列表update_user_list()print(f'{username} 加入了聊天室,当前在线人数: {len(online_users)}')# 接收消息
@socketio.on('message')
def handle_message(data):user_id = data['userId']if user_id in online_users:# 广播消息emit('message', {'userId': user_id,'username': online_users[user_id]['username'],'message': data['message']}, broadcast=True)# 用户断开连接
@socketio.on('disconnect')
def handle_disconnect():# 查找断开连接的用户disconnected_user = Nonefor user_id, user_info in online_users.items():if user_info['sid'] == request.sid:disconnected_user = {'user_id': user_id,'username': user_info['username']}break# 如果找到用户,移除并广播离开消息if disconnected_user:del online_users[disconnected_user['user_id']]# 广播用户离开消息emit('message', {'userId': 'system','username': '系统','message': f'{disconnected_user["username"]} 离开了聊天室'}, broadcast=True)# 更新在线用户列表update_user_list()print(f'{disconnected_user["username"]} 离开了聊天室,当前在线人数: {len(online_users)}')# 更新在线用户列表
def update_user_list():# 转换用户数据users_data = [{'userId': user_id,'username': user_info['username']}for user_id, user_info in online_users.items()]# 发送更新emit('update_users', {'count': len(online_users),'users': users_data}, broadcast=True)if __name__ == '__main__':print('服务器启动中...')socketio.run(app, debug=True, host='0.0.0.0', port=5000)
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>简单聊天室</title><script src="https://cdn.tailwindcss.com"></script><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><script>tailwind.config = {theme: {extend: {colors: {primary: '#4F46E5',secondary: '#6366F1',accent: '#818CF8',dark: '#1F2937',light: '#F3F4F6',},fontFamily: {sans: ['Inter', 'system-ui', 'sans-serif'],},}}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.scrollbar-hide {-ms-overflow-style: none;scrollbar-width: none;}.scrollbar-hide::-webkit-scrollbar {display: none;}.message-appear {animation: fadeIn 0.3s ease-in-out;}@keyframes fadeIn {from { opacity: 0; transform: translateY(10px); }to { opacity: 1; transform: translateY(0); }}}</style>
</head>
<body class="bg-gray-50 min-h-screen flex flex-col"><!-- 顶部导航栏 --><header class="bg-primary text-white shadow-md"><div class="container mx-auto px-4 py-3 flex justify-between items-center"><h1 class="text-xl font-bold flex items-center"><i class="fa fa-comments-o mr-2"></i>简单聊天室</h1><div class="flex items-center"><span id="user-count" class="bg-white text-primary px-3 py-1 rounded-full text-sm font-medium"><i class="fa fa-user-o mr-1"></i> 0 在线</span></div></div></header><!-- 主要内容区 --><main class="flex-grow container mx-auto px-4 py-6 flex flex-col lg:flex-row gap-6"><!-- 聊天室区域 --><div class="flex-grow bg-white rounded-xl shadow-lg overflow-hidden flex flex-col"><!-- 消息显示区域 --><div id="message-container" class="flex-grow p-4 overflow-y-auto scrollbar-hide space-y-4"><div class="text-center text-gray-500 py-8"><i class="fa fa-comments text-4xl mb-2 opacity-30"></i><p>欢迎来到聊天室!开始聊天吧</p></div></div><!-- 分隔线 --><div class="border-t border-gray-200"></div><!-- 消息输入区域 --><div class="p-4"><div class="flex gap-3"><input type="text" id="message-input" placeholder="输入消息..." class="flex-grow px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all"><button id="send-button" class="bg-primary hover:bg-primary/90 text-white px-6 py-3 rounded-lg font-medium transition-all flex items-center justify-center"><i class="fa fa-paper-plane mr-2"></i> 发送</button></div></div></div><!-- 在线用户列表 --><div class="lg:w-80 bg-white rounded-xl shadow-lg overflow-hidden flex flex-col"><div class="bg-secondary text-white p-4"><h2 class="font-bold flex items-center"><i class="fa fa-users mr-2"></i> 在线用户</h2></div><div id="user-list" class="flex-grow p-4 overflow-y-auto scrollbar-hide space-y-2"><div class="text-center text-gray-500 py-8"><p>暂无在线用户</p></div></div></div></main><!-- 底部信息 --><footer class="bg-dark text-white py-4"><div class="container mx-auto px-4 text-center text-sm"><p>© 2023 简单聊天室 | 使用 Flask 和 Socket.IO 构建</p></div></footer><!-- Socket.IO 客户端 --><script src="https://cdn.socket.io/4.4.1/socket.io.min.js"></script><script>// 连接到服务器const socket = io();// 获取DOM元素const messageContainer = document.getElementById('message-container');const messageInput = document.getElementById('message-input');const sendButton = document.getElementById('send-button');const userList = document.getElementById('user-list');const userCount = document.getElementById('user-count');// 生成唯一用户IDlet userId = localStorage.getItem('chatUserId');if (!userId) {userId = Math.random().toString(36).substring(2, 10);localStorage.setItem('chatUserId', userId);}// 生成随机用户名let username = localStorage.getItem('chatUsername');if (!username) {const adjectives = ['快乐', '聪明', '可爱', '机灵', '热情', '友善', '阳光', '优雅', '风趣', '温柔'];const animals = ['小猫', '小狗', '小鸟', '小兔', '小熊', '小鹿', '小鱼', '小猴', '小马', '小羊'];username = `${adjectives[Math.floor(Math.random() * adjectives.length)]}的${animals[Math.floor(Math.random() * animals.length)]}`;localStorage.setItem('chatUsername', username);}// 连接成功socket.on('connect', () => {console.log('已连接到服务器');// 发送用户信息socket.emit('join', { userId, username });// 自动聚焦到输入框messageInput.focus();});// 连接断开socket.on('disconnect', () => {console.log('已断开与服务器的连接');addSystemMessage('与服务器的连接已断开,正在尝试重新连接...');});// 重新连接socket.on('reconnect', () => {console.log('已重新连接到服务器');socket.emit('join', { userId, username });addSystemMessage('已重新连接到服务器');});// 接收消息socket.on('message', (data) => {addMessage(data);});// 更新用户列表socket.on('update_users', (data) => {updateUserList(data.users);userCount.innerHTML = `<i class="fa fa-user-o mr-1"></i> ${data.count} 在线`;});// 添加系统消息function addSystemMessage(text) {const messageDiv = document.createElement('div');messageDiv.className = 'text-center text-gray-500 message-appear';messageDiv.innerHTML = `<p><i class="fa fa-info-circle mr-1"></i> ${text}</p>`;messageContainer.appendChild(messageDiv);messageContainer.scrollTop = messageContainer.scrollHeight;}// 添加聊天消息function addMessage(data) {const isSelf = data.userId === userId;const messageDiv = document.createElement('div');messageDiv.className = `flex ${isSelf ? 'justify-end' : 'justify-start'} message-appear`;const contentDiv = document.createElement('div');contentDiv.className = isSelf ? 'bg-primary text-white rounded-lg p-3 max-w-[80%]' : 'bg-gray-100 text-gray-800 rounded-lg p-3 max-w-[80%]';if (!isSelf) {const usernameSpan = document.createElement('span');usernameSpan.className = 'font-bold text-sm mb-1 block';usernameSpan.textContent = data.username;contentDiv.appendChild(usernameSpan);}const messageSpan = document.createElement('span');messageSpan.textContent = data.message;contentDiv.appendChild(messageSpan);messageDiv.appendChild(contentDiv);messageContainer.appendChild(messageDiv);messageContainer.scrollTop = messageContainer.scrollHeight;}// 更新用户列表function updateUserList(users) {userList.innerHTML = '';if (users.length === 0) {const emptyDiv = document.createElement('div');emptyDiv.className = 'text-center text-gray-500 py-8';emptyDiv.innerHTML = '<p>暂无在线用户</p>';userList.appendChild(emptyDiv);return;}users.forEach(user => {const userDiv = document.createElement('div');userDiv.className = `flex items-center p-2 rounded-lg ${user.userId === userId ? 'bg-primary/10 text-primary' : 'hover:bg-gray-100'}`;const iconSpan = document.createElement('span');iconSpan.className = 'w-2 h-2 rounded-full bg-green-500 mr-2';const nameSpan = document.createElement('span');nameSpan.textContent = user.username;userDiv.appendChild(iconSpan);userDiv.appendChild(nameSpan);userList.appendChild(userDiv);});}// 发送消息function sendMessage() {const message = messageInput.value.trim();if (message) {socket.emit('message', {userId,username,message});messageInput.value = '';}}// 事件监听sendButton.addEventListener('click', sendMessage);messageInput.addEventListener('keypress', (e) => {if (e.key === 'Enter') {sendMessage();}});</script>
</body>
</html>
实现原理
这个简单聊天室基于以下技术实现:
-
后端技术:
- 使用 Flask 作为 Web 框架
- 使用 Flask-SocketIO 处理 WebSocket 通信
- 内存存储在线用户信息
-
前端技术:
- HTML/CSS/JavaScript 构建界面
- Tailwind CSS 实现样式设计
- Socket.IO 客户端处理实时通信
-
通信机制:
- WebSocket 实现实时双向通信
- 自定义事件处理消息发送和接收
- 状态同步机制更新在线用户列表
关键代码解析
1. 后端消息处理
# 接收消息
@socketio.on('message')
def handle_message(data):user_id = data['userId']if user_id in online_users:# 广播消息emit('message', {'userId': user_id,'username': online_users[user_id]['username'],'message': data['message']}, broadcast=True)
2. 用户连接管理
# 用户加入
@socketio.on('join')
def handle_join(data):user_id = data['userId']username = data['username']# 存储用户信息online_users[user_id] = {'username': username,'sid': request.sid}# 广播用户加入消息emit('message', {'userId': 'system','username': '系统','message': f'{username} 加入了聊天室'}, broadcast=True)# 更新在线用户列表update_user_list()
3. 前端消息处理
// 接收消息
socket.on('message', (data) => {addMessage(data);
});// 添加聊天消息
function addMessage(data) {const isSelf = data.userId === userId;const messageDiv = document.createElement('div');messageDiv.className = `flex ${isSelf ? 'justify-end' : 'justify-start'} message-appear`;const contentDiv = document.createElement('div');contentDiv.className = isSelf ? 'bg-primary text-white rounded-lg p-3 max-w-[80%]' : 'bg-gray-100 text-gray-800 rounded-lg p-3 max-w-[80%]';if (!isSelf) {const usernameSpan = document.createElement('span');usernameSpan.className = 'font-bold text-sm mb-1 block';usernameSpan.textContent = data.username;contentDiv.appendChild(usernameSpan);}const messageSpan = document.createElement('span');messageSpan.textContent = data.message;contentDiv.appendChild(messageSpan);messageDiv.appendChild(contentDiv);messageContainer.appendChild(messageDiv);messageContainer.scrollTop = messageContainer.scrollHeight;
}
4. 前端发送消息
// 发送消息
function sendMessage() {const message = messageInput.value.trim();if (message) {socket.emit('message', {userId,username,message});messageInput.value = '';}
}// 事件监听
sendButton.addEventListener('click', sendMessage);messageInput.addEventListener('keypress', (e) => {if (e.key === 'Enter') {sendMessage();}
});
使用说明
安装依赖:
pip install flask flask-socketio
运行服务器:
python app.py
访问聊天室:
- 在浏览器中打开
http://localhost:5000
,即可进入聊天室。可以在多个浏览器窗口或标签页中打开,模拟多个用户同时在线聊天。
聊天功能:
- 在输入框中输入消息,按 Enter 或点击 "发送" 按钮发送
- 可以看到其他用户发送的消息
- 右侧显示在线用户列表
- 系统会提示用户加入或离开
扩展建议
-
增强功能:
- 添加用户认证和登录系统
- 实现私聊功能
- 添加消息撤回和编辑功能
- 支持发送图片和文件
-
性能优化:
- 使用 Redis 等外部存储管理在线用户
- 添加消息队列处理大量消息
- 实现消息分页加载
-
用户界面:
- 添加表情符号支持
- 实现消息通知功能
- 添加深色 / 浅色主题切换
- 优化移动端显示效果
-
安全增强:
- 添加输入过滤防止 XSS 攻击
- 实现消息加密
- 添加反垃圾消息机制