当前位置: 首页 > article >正文

FastAPI WebSocket 聊天应用详细教程

在这里插入图片描述
在这里插入图片描述

项目简介

这是一个基于 FastAPI 和 WebSocket 实现的实时聊天应用,支持一对一聊天、离线消息存储等功能。

技术栈

  • 后端:FastAPI (Python)
  • 前端:HTML、JavaScript、CSS
  • 通信:WebSocket
  • 认证:简单的 token 认证

项目结构

├── main.py              # 后端主程序
└── templates/           # 前端模板目录└── chat.html       # 聊天页面

详细代码实现

1. 后端实现 (main.py)

from fastapi import FastAPI, WebSocket, Depends, WebSocketDisconnect
from typing import Dict
import json
from fastapi.responses import HTMLResponse, FileResponse
from fastapi.staticfiles import StaticFilesapp = FastAPI()
app.mount("/templates", StaticFiles(directory="templates"), name="templates")class ConnectionManager:def __init__(self):self.active_connections: Dict[str, WebSocket] = {}self.offline_messages: Dict[str, list] = {}  # 离线消息存储async def connect(self, user: str, websocket: WebSocket):await websocket.accept()self.active_connections[user] = websocketasync def disconnect(self, user: str, websocket: WebSocket):if user in self.active_connections:del self.active_connections[user]async def send_personal_message(self, message: str, to_user: str):if to_user in self.active_connections:await self.active_connections[to_user].send_text(message)return {"success": True}else:# 存储离线消息if to_user not in self.offline_messages:self.offline_messages[to_user] = []self.offline_messages[to_user].append(message)return {"success": False, "reason": "offline", "stored": True}async def get_offline_messages(self, user: str):if user in self.offline_messages:messages = self.offline_messages[user]del self.offline_messages[user]  # 取出后删除return messagesreturn []# Token 认证
async def get_cookie_or_token(token: str = None):if not token:from fastapi import HTTPExceptionraise HTTPException(status_code=401, detail="未授权")return token# 初始化连接管理器
ws_manager = ConnectionManager()@app.websocket("/ws/{user}")
async def websocket_one_to_one(websocket: WebSocket,user: str,cookie_or_token: str = Depends(get_cookie_or_token)
):await ws_manager.connect(user, websocket)# 发送离线消息offline_msgs = await ws_manager.get_offline_messages(user)for msg in offline_msgs:await websocket.send_text(msg)try:while True:data = await websocket.receive_text()message_data = json.loads(data)# 防止自己给自己发消息if message_data["to_user"] == user:error_msg = {"type": "error","content": "不能发送消息给自己"}await websocket.send_text(json.dumps(error_msg))continueresponse_message = {"from": user,"content": message_data["content"],"timestamp": message_data.get("timestamp"),"type": "message"}# 发送消息result = await ws_manager.send_personal_message(message=json.dumps(response_message),to_user=message_data["to_user"])# 处理离线消息if not result["success"]:if result.get("stored"):success_msg = {"type": "info","content": f"消息已保存,将在用户 {message_data['to_user']} 上线时送达"}await websocket.send_text(json.dumps(success_msg))except WebSocketDisconnect:await ws_manager.disconnect(user, websocket)except Exception as e:print(f"WebSocket 错误: {e}")await ws_manager.disconnect(user, websocket)@app.get("/", response_class=FileResponse)
async def root():return "templates/chat.html"

2. 前端实现 (templates/chat.html)

<!DOCTYPE html>
<html>
<head><title>WebSocket Chat</title><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;}#loginSection {text-align: center;margin-top: 100px;}#chatSection {display: none;}#messages {list-style-type: none;padding: 0;height: 400px;overflow-y: auto;border: 1px solid #ccc;margin-bottom: 20px;padding: 10px;}.message {margin: 10px 0;padding: 10px;border-radius: 5px;background-color: #f0f0f0;}.input-group {margin-bottom: 10px;}input[type="text"] {padding: 8px;margin-right: 10px;width: 200px;}button {padding: 8px 15px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;}button:hover {background-color: #45a049;}#logoutBtn {background-color: #f44336;}#logoutBtn:hover {background-color: #da190b;}.error {color: red;margin: 10px 0;}.info {color: blue;margin: 10px 0;}</style>
</head>
<body><!-- 登录部分 --><div id="loginSection"><h1>WebSocket 聊天</h1><div class="input-group"><input type="text" id="loginUsername" placeholder="输入用户名" autocomplete="off"/><button onclick="login()">登录</button></div><div id="loginError" class="error"></div></div><!-- 聊天部分 --><div id="chatSection"><h1>WebSocket 聊天</h1><div id="currentUser"></div><form action="" onsubmit="sendMessage(event)"><div class="input-group"><input type="text" id="messageText" placeholder="输入消息" autocomplete="off"/><input type="text" id="username" placeholder="接收者用户名" autocomplete="off"/><button type="submit">发送</button></div></form><button id="logoutBtn" onclick="logout()">退出</button><ul id='messages'></ul></div><script>let ws = null;function checkLogin() {const token = localStorage.getItem('token');if (token) {document.getElementById('loginSection').style.display = 'none';document.getElementById('chatSection').style.display = 'block';document.getElementById('currentUser').textContent = `当前用户: ${token}`;connectWebSocket();} else {document.getElementById('loginSection').style.display = 'block';document.getElementById('chatSection').style.display = 'none';}}function login() {const username = document.getElementById('loginUsername').value.trim();if (!username) {document.getElementById('loginError').textContent = '请输入用户名';return;}localStorage.setItem('token', username);checkLogin();}function logout() {if (ws && ws.readyState === WebSocket.OPEN) {ws.close();}localStorage.removeItem('token');document.getElementById('messages').innerHTML = '';document.getElementById('messageText').value = '';document.getElementById('username').value = '';document.getElementById('loginSection').style.display = 'block';document.getElementById('chatSection').style.display = 'none';document.getElementById('loginUsername').value = '';document.getElementById('currentUser').textContent = '';}function connectWebSocket() {const token = localStorage.getItem('token');ws = new WebSocket(`ws://localhost:8000/ws/${token}?token=${token}`);ws.onmessage = function(event) {const messages = document.getElementById('messages');const message = document.createElement('li');message.className = 'message';const data = JSON.parse(event.data);if (data.type === 'error') {message.style.color = 'red';message.textContent = data.content;} else if (data.type === 'info') {message.style.color = 'blue';message.textContent = data.content;} else {message.textContent = `${data.from}: ${data.content}`;}messages.appendChild(message);messages.scrollTop = messages.scrollHeight;};ws.onerror = function(error) {console.error('WebSocket 错误:', error);};ws.onclose = function() {console.log('WebSocket 连接已关闭');};}function sendMessage(event) {event.preventDefault();const messageInput = document.getElementById("messageText");const usernameInput = document.getElementById("username");if (!messageInput.value.trim() || !usernameInput.value.trim()) {return;}const messageData = {content: messageInput.value,to_user: usernameInput.value,timestamp: new Date().toISOString()};ws.send(JSON.stringify(messageData));const messages = document.getElementById('messages');const message = document.createElement('li');message.className = 'message';message.style.backgroundColor = '#e3f2fd';message.textContent = `我: ${messageInput.value}`;messages.appendChild(message);messages.scrollTop = messages.scrollHeight;messageInput.value = '';}checkLogin();</script>
</body>
</html>

功能特点

  1. 用户管理

    • 简单的用户名登录系统
    • 用户在线状态管理
    • 用户会话保持
  2. 消息功能

    • 实时一对一聊天
    • 离线消息存储
    • 上线自动接收离线消息
    • 防止自己给自己发消息
  3. 界面特性

    • 响应式设计
    • 消息实时显示
    • 错误信息提示
    • 消息状态反馈
  4. 技术特性

    • WebSocket 实时通信
    • 状态管理
    • 错误处理
    • 会话管理

如何运行

  1. 安装依赖
pip install fastapi uvicorn
  1. 启动服务器
uvicorn main:app --reload
  1. 访问应用
  • 打开浏览器访问 http://localhost:8000
  • 输入用户名登录
  • 开始聊天

使用流程

  1. 登录

    • 输入用户名
    • 点击登录按钮
  2. 发送消息

    • 输入接收者用户名
    • 输入消息内容
    • 点击发送
  3. 接收消息

    • 实时接收其他用户发送的消息
    • 上线时接收离线消息
  4. 退出

    • 点击退出按钮
    • 清除登录状态

相关文章:

FastAPI WebSocket 聊天应用详细教程

项目简介 这是一个基于 FastAPI 和 WebSocket 实现的实时聊天应用&#xff0c;支持一对一聊天、离线消息存储等功能。 技术栈 后端&#xff1a;FastAPI (Python)前端&#xff1a;HTML、JavaScript、CSS通信&#xff1a;WebSocket认证&#xff1a;简单的 token 认证 项目结构…...

(区间 dp)洛谷 P6879 JOI2020 Collecting Stamps 3 题解

题意 给定一个周长为 L L L 的圆&#xff0c;从一个点出发&#xff0c;有 N N N 个黑白熊雕像&#xff0c;编号为 1 1 1 到 N N N&#xff0c;第 i i i 个雕像在顺时针 X i X_i Xi​ 米处&#xff0c;如果你没有在 T i T_i Ti​ 秒内收集到这个黑白熊雕像&#xff0c;那…...

vue3+canvas裁剪框样式【前端】

目录 canvas绘制裁剪框&#xff1a;拖拽改变框的大小&#xff1a;圆圈样式&#xff1a;方块样式&#xff1a; canvas绘制裁剪框&#xff1a; // 绘制裁剪框 const drawCropRect (ctx: CanvasRenderingContext2D): void > {if (cropRect.value.width > 0 && crop…...

【Vue3 / TypeScript】 项目兼容低版本浏览器的全面指南

在当今前端开发领域&#xff0c;Vue3 和 TypeScript 已成为主流技术栈。然而&#xff0c;随着 JavaScript 语言的快速演进&#xff0c;许多现代特性在低版本浏览器中无法运行。本文将详细介绍如何使 Vue3 TypeScript 项目完美兼容 IE11 等低版本浏览器。 一、理解兼容性挑战 …...

软件功能测试和非功能测试有什么区别和联系?

软件测试是保障软件质量的核心环节&#xff0c;而软件功能测试和非功能测试作为测试领域的两大重要组成部分&#xff0c;承担着不同但又相互关联的职责。 软件功能测试指的是通过验证软件系统的各项功能是否按照需求规格说明书来正确实现&#xff0c;确保软件的功能和业务流程…...

10_C++入门案例习题: 结构体案例

案例描述 学校正在做毕设项目&#xff0c;每名老师带领5个学生&#xff0c;总共有3名老师&#xff0c;需求如下 设计学生和老师的结构体&#xff0c;其中在老师的结构体中&#xff0c;有老师姓名和一个存放5名学生的数组作为成员 学生的成员有姓名、考试分数&#xff0c; 创建…...

快速定位达梦缓存的执行计划并清理

开发告诉你一个sql慢&#xff0c;你想看看缓存中执行计划时&#xff0c;怎么精准快速定位&#xff1f; 可能一般人通过文本内容模糊搜索 select cache_item, substr(sqlstr,1,60)stmt from v$cachepln where sqlstr like %YOUR SQL STRING%; 搜出来的内容比较多&#xff0c;研…...

Spring中配置 Bean 的两种方式:XML 配置 和 Java 配置类

在 Spring 框架中,配置 Bean 的方式主要有两种:XML 配置 和 Java 配置类。这两种方式都可以实现将对象注册到 Spring 容器中,并通过依赖注入进行管理。本文将详细介绍这两种配置方式的步骤,并提供相应的代码示例。 1. 使用 XML 配置的方式 步骤 创建 Spring 配置文件 创建…...

AI算子开发是什么

AI算子开发是指为人工智能&#xff08;尤其是深度学习&#xff09;模型中的基础计算单元&#xff08;如卷积、矩阵乘法、激活函数等&#xff09;设计并优化其底层实现的过程。这些计算单元被称为“算子”&#xff08;Operator&#xff09;&#xff0c;它们是构建神经网络的核心…...

低光环境下双目云台摄像头监控性能解析

双目云台摄像头在低光环境下的监控效果主要取决于其硬件配置和软件优化能力。以下是对双目云台摄像头在低光环境下监控效果的详细分析&#xff1a; 一、硬件配置对低光监控效果的影响 镜头与焦距 &#xff1a; 双目云台摄像头通常配备超大广角固定镜头和360视角的移动镜头&a…...

若依、vben-admin、三维可视化

对三维可视化&#xff0c;包括cesium、模型加载、GIS有关的项目和技术都可以私信&#xff0c;包括基础数据后台管理系统的搭建和配置...

如何Ubuntu 22.04.5 LTS 64 位 操作系统部署运行SLAM3! 详细流程

以下是在本地部署运行 ORB-SLAM3 的详细步骤&#xff0c;基于官方 README.md 和最佳实践整理&#xff0c;适用于 Ubuntu 16.04/18.04/20.04/22.04 系统&#xff1a; 一、系统要求与依赖项安装 1. 基础系统要求 操作系统&#xff1a;Ubuntu 16.04/18.04/20.04/22.04&#xff…...

LLMs可在2位精度下保持高准确率

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

爆改 toxml 组件 支持数据双向绑定 解决数据刷新问题

GGGGGGGGGGGGGGGGGithub地址自行研究 sbfkcel/towxml: 微信小程序HTML、Markdown渲染库https://github.com/sbfkcel/towxml原组件是以导入数据渲染信息为目的、本文以AI数据返回小程序为模拟效果演示 默认情况只在ready 环节进行渲染静态资源 1、对传入数据容器的位置做处理 …...

Unreal如何使用后处理材质实现一个黑屏渐变效果

文章目录 前言相机后期处理材质创建材质相机设置动态修改FadeAlpha参数使用示例最后前言 UE5 开发VR ,如何通过PostProcess轻松实现黑屏渐变效果 最简单的办法,其实是使用一个半球形模型,遮挡住相机,然后控制这个半球形遮罩的颜色透明度,至少Unity中默认的Tunneling是这么…...

施磊老师基于muduo网络库的集群聊天服务器(四)

文章目录 实现登录业务登录业务代码补全数据库接口:查询,更新状态注意学习一下里面用到的数据库api测试与问题**问题1:****问题2:** 用户连接信息与线程安全聊天服务器是长连接服务器如何找到用户B的连接&#xff1f;在业务层存储用户的连接信息多线程安全问题加锁! 处理客户端…...

Java多线程编程初阶指南

目录 一.线程基础概念 线程是什么&#xff1f; 线程与进程对比 为啥要有线程 二.线程实现方式 继承Thread类 实现Runnable接口 常规实现方式 匿名内部类写法 Lambda表达式写法&#xff08;Java8&#xff09; 对比总结 三.Thread 类及常见方法 核心功能 核心构造方…...

DB-GPT支持mcp协议配置说明

简介 在 DB-GPT 中使用 MCP&#xff08;Model Context Protocol&#xff09;协议&#xff0c;主要通过配置 MCP 服务器和智能体协作实现外部工具集成与数据交互。 开启mcp服务&#xff0c;这里以网页抓取为例 npx -y supergateway --stdio "uvx mcp-server-fetch" …...

CoT-Drive:利用 LLM 和思维链提示实现自动驾驶的高效运动预测

25年3月来自澳门大学和 MIT 的论文“CoT-Drive: Efficient Motion Forecasting for Autonomous Driving with LLMs and Chain-of-Thought Prompting”。 准确的运动预测对于安全的自动驾驶 (AD) 至关重要。本研究提出 CoT-Drive&#xff0c;这是一种利用大语言模型 (LLM) 和思…...

Flowable7.x学习笔记(十)分页查询已部署 BPMN XML 流程

前言 上一篇文章我们已经完成了流程的部署功能&#xff0c;那么下一步就是要激活流程了&#xff0c;但是我们要需要明确的指定具体要激活部署后的哪一条流程&#xff0c;所以我们先把已部署的基础信息以及具体定义信息分页查询出来&#xff0c;本文先把基础代码生成以及完成分页…...

【仓颉 + 鸿蒙 + AI Agent】CangjieMagic框架(15):NaiveExecutor

CangjieMagic框架&#xff1a;使用华为仓颉编程语言编写&#xff0c;专门用于开发AI Agent&#xff0c;支持鸿蒙、Windows、macOS、Linux等系统。 这篇文章剖析一下 CangjieMagic 框架中的 NaiveExecutor。 1 NaiveExecutor是什么&#xff1f; #mermaid-svg-u9WgSijieH1Pk0xU…...

Office文档图片批量提取工具

Office.Files.Images 是一款专注于从 Word、Excel、PPT 等 Office 文档中批量提取图片的轻量级工具&#xff0c;支持 .docx、.xlsx、.pptx 格式文件。该软件体积仅 ‌343KB‌&#xff0c;无需安装即可运行&#xff0c;通过拖拽操作实现快速解析与导出&#xff0c;尤其适合需批量…...

33-公交车司机管理系统

技术&#xff1a; 基于 B/S 架构 SpringBootMySQLvueelementui 环境&#xff1a; Idea mysql maven jdk1.8 node 用户端功能 1.首页:展示车辆信息及车辆位置和线路信息 2.模块:车辆信息及车辆位置和线路信息 3.公告、论坛 4.在线留言 5.个人中心:修改个人信息 司机端功能…...

PyCharm 初级教程:从安装到第一个 Python 项目

作为 Python 程序员&#xff0c;无论是刚入门还是工作多年&#xff0c;PyCharm 都是一个绕不开的开发工具。它是 JetBrains 出品的一款强大的 Python IDE&#xff0c;有自动补全、调试、虚拟环境支持、代码检查等等功能&#xff0c;体验比命令行 记事本舒服一百倍。 今天这篇…...

文件上传漏洞3

1. 例题:文件上传限制 1&#xff09;上传漏洞靶场介绍 项目名称: upload-labs开发语言: 使用PHP语言编写功能定位: 专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场关卡数量: 目前共21关&#xff0c;每关包含不同上传方式注意事项: 每关没有固定通关方法&#xff0c;不要自限…...

QML FontDialog:使用FontDialog实现字体选择功能

目录 引言相关阅读FontDialog基本介绍字体属性 实例演示项目结构代码实现Main.qmlmain.cpp 代码解析运行效果 总结 引言 在桌面应用程序开发中&#xff0c;字体选择是一个常见的需求。Qt Quick提供了FontDialog组件来实现这一功能。本文将介绍如何在Qt Quick应用程序中使用Fon…...

力扣刷题Day 27:环形链表(141)

1.题目描述 2.思路 创建一个结点集合&#xff0c;遍历链表&#xff0c;如果遇到已经加进集合的结点就说明链表有环。 3.代码&#xff08;Python3&#xff09; class Solution:def hasCycle(self, head: Optional[ListNode]) -> bool:node headnode_set set()while node…...

1.1软考系统架构设计师:系统架构的定义与作用 - 超简记忆要点、知识体系全解、考点深度解析、真题训练附答案及解析

超简记忆要点 定义&#xff1a;结构决策 | 抽象概念 | 多视图模型&#xff08;逻辑/物理/动态&#xff09;作用&#xff1a;解耦复杂需求 | 集成扩展 | 指导开发&#xff08;蓝图&#xff09;要素&#xff1a;构件&#xff08;原子/复合&#xff09; | 连接件&#xff08;API/…...

研发效率破局之道阅读总结(3)工程优化

研发效率破局之道阅读总结(3)工程优化 Author: Once Day Date: 2025年4月22日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 程序的艺术_Once-Day…...

metasploit(2)生成dll木马

声明&#xff01;本文章所有的工具分享仅仅只是供大家学习交流为主&#xff0c;切勿用于非法用途&#xff0c;如有任何触犯法律的行为&#xff0c;均与本人及团队无关&#xff01;&#xff01;&#xff01; 一、dll文件基本概念 DLL 是一种包含可由多个程序同时使用的代码和数…...