用python streamlit sqlite3 写一个聊天室

图:

代码:

python 复制代码
import streamlit as st
import sqlite3
import datetime
from typing import List, Tuple
import time
import os
import io
from PIL import Image
import base64

# 创建uploads目录
if not os.path.exists('uploads'):
    os.makedirs('uploads')

# 数据库初始化
def init_db():
    conn = sqlite3.connect('chat.db', check_same_thread=False)
    cursor = conn.cursor()
    
    # 创建用户表
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # 创建消息表(修改以支持文件类型)
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS messages (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER,
            message_type TEXT DEFAULT 'text', -- text, image, file
            message_content TEXT, -- 文本内容或文件路径
            file_name TEXT, -- 原始文件名
            file_size INTEGER, -- 文件大小
            timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users (id)
        )
    ''')
    
    conn.commit()
    return conn

# 用户管理函数
def add_user(username: str, conn) -> int:
    cursor = conn.cursor()
    try:
        cursor.execute('INSERT INTO users (username) VALUES (?)', (username,))
        conn.commit()
        return cursor.lastrowid
    except sqlite3.IntegrityError:
        # 用户已存在,返回现有用户ID
        cursor.execute('SELECT id FROM users WHERE username = ?', (username,))
        return cursor.fetchone()[0]

def get_user_id(username: str, conn) -> int:
    cursor = conn.cursor()
    cursor.execute('SELECT id FROM users WHERE username = ?', (username,))
    result = cursor.fetchone()
    return result[0] if result else None

# 消息管理函数
def add_message(user_id: int, message_type: str, message_content: str, file_name: str = None, file_size: int = None, conn=None):
    cursor = conn.cursor()
    cursor.execute('''
        INSERT INTO messages (user_id, message_type, message_content, file_name, file_size) 
        VALUES (?, ?, ?, ?, ?)
    ''', (user_id, message_type, message_content, file_name, file_size))
    conn.commit()

def get_recent_messages(limit: int = 50, conn=None) -> List[Tuple]:
    cursor = conn.cursor()
    cursor.execute('''
        SELECT m.id, u.username, m.message_type, m.message_content, 
               m.file_name, m.file_size, m.timestamp 
        FROM messages m 
        JOIN users u ON m.user_id = u.id 
        ORDER BY m.timestamp DESC 
        LIMIT ?
    ''', (limit,))
    return cursor.fetchall()

# 文件处理函数
def save_uploaded_file(uploaded_file) -> str:
    """保存上传的文件并返回文件路径"""
    # 生成唯一文件名
    timestamp = int(time.time())
    file_extension = os.path.splitext(uploaded_file.name)[1]
    filename = f"{timestamp}_{uploaded_file.name}"
    filepath = os.path.join('uploads', filename)
    
    # 保存文件
    with open(filepath, "wb") as f:
        f.write(uploaded_file.getbuffer())
    
    return filepath

def get_file_size(file_path: str) -> int:
    """获取文件大小"""
    return os.path.getsize(file_path)

def format_file_size(size_bytes: int) -> str:
    """格式化文件大小"""
    if size_bytes == 0:
        return "0B"
    size_names = ["B", "KB", "MB", "GB"]
    i = 0
    while size_bytes >= 1024 and i < len(size_names) - 1:
        size_bytes /= 1024.0
        i += 1
    return f"{size_bytes:.1f}{size_names[i]}"

# 图片处理函数
def resize_image(image_data, max_size=(400, 400)):
    """调整图片大小"""
    image = Image.open(io.BytesIO(image_data))
    image.thumbnail(max_size, Image.Resampling.LANCZOS)
    
    # 转换为base64
    buffered = io.BytesIO()
    image.save(buffered, format="PNG")
    img_str = base64.b64encode(buffered.getvalue()).decode()
    return img_str

# 主应用
def main():
    st.set_page_config(
        page_title="聊天室",
        page_icon="💬",
        layout="wide"
    )
    
    # 初始化数据库
    conn = init_db()
    
    # 自定义CSS样式
    st.markdown("""
    <style>
    .chat-container {
        max-height: 500px;
        overflow-y: auto;
        border: 1px solid #ddd;
        border-radius: 10px;
        padding: 10px;
        margin-bottom: 20px;
        background-color: #f9f9f9;
    }
    .message {
        margin: 10px 0;
        padding: 10px;
        border-radius: 10px;
        background-color: #e3f2fd;
    }
    .own-message {
        background-color: #c8e6c9;
        margin-left: 20%;
    }
    .other-message {
        background-color: #f5f5f5;
        margin-right: 20%;
    }
    .timestamp {
        font-size: 0.8em;
        color: #666;
        float: right;
    }
    .username {
        font-weight: bold;
        color: #1976d2;
    }
    .file-message {
        background-color: #fff3e0;
        border-left: 4px solid #ff9800;
    }
    .image-message {
        background-color: #e8f5e8;
        border-left: 4px solid #4caf50;
    }
    .uploaded-image {
        max-width: 100%;
        max-height: 300px;
        border-radius: 5px;
        margin: 5px 0;
    }
    .file-info {
        font-size: 0.9em;
        color: #666;
        margin-top: 5px;
    }
    </style>
    """, unsafe_allow_html=True)
    
    # 标题
    st.title("💬 实时聊天室(支持图片和文件)")
    
    # 用户会话状态
    if 'user_id' not in st.session_state:
        st.session_state.user_id = None
    if 'username' not in st.session_state:
        st.session_state.username = None
    
    # 用户登录/注册
    if st.session_state.user_id is None:
        st.subheader("登录/注册")
        username = st.text_input("请输入用户名:", max_chars=20)
        
        if st.button("进入聊天室") and username:
            if len(username.strip()) > 0:
                st.session_state.user_id = add_user(username.strip(), conn)
                st.session_state.username = username.strip()
                st.rerun()
            else:
                st.error("用户名不能为空")
    else:
        # 显示当前用户信息
        col1, col2 = st.columns([3, 1])
        with col1:
            st.success(f"欢迎, {st.session_state.username}!")
        with col2:
            if st.button("退出"):
                st.session_state.user_id = None
                st.session_state.username = None
                st.rerun()
        
        # 聊天界面
        st.subheader("聊天室")
        
        # 自动刷新消息
        if 'last_refresh' not in st.session_state:
            st.session_state.last_refresh = time.time()
        
        # 消息显示区域
        messages_container = st.container()
        
        with messages_container:
            st.markdown("<div class='chat-container'>", unsafe_allow_html=True)
            
            messages = get_recent_messages(50, conn)
            messages.reverse()  # 按时间顺序显示
            
            for msg_id, username, message_type, message_content, file_name, file_size, timestamp in messages:
                is_own_message = (username == st.session_state.username)
                
                if message_type == 'text':
                    message_class = "own-message" if is_own_message else "other-message"
                    st.markdown(f"""
                    <div class="message {message_class}">
                        <span class="username">{username}</span>: {message_content}
                        <span class="timestamp">{timestamp}</span>
                    </div>
                    """, unsafe_allow_html=True)
                
                elif message_type == 'image':
                    message_class = "image-message"
                    if is_own_message:
                        message_class += " own-message"
                    else:
                        message_class += " other-message"
                    
                    # 显示图片
                    if os.path.exists(message_content):
                        with open(message_content, "rb") as f:
                            image_data = f.read()
                        img_str = resize_image(image_data)
                        
                        st.markdown(f"""
                        <div class="message {message_class}">
                            <span class="username">{username}</span> 发送了一张图片:
                            <br>
                            <img src="data:image/png;base64,{img_str}" class="uploaded-image">
                            <span class="timestamp">{timestamp}</span>
                        </div>
                        """, unsafe_allow_html=True)
                
                elif message_type == 'file':
                    message_class = "file-message"
                    if is_own_message:
                        message_class += " own-message"
                    else:
                        message_class += " other-message"
                    
                    # 显示文件信息和下载链接
                    file_size_str = format_file_size(file_size)
                    
                    # 检查文件是否存在
                    if os.path.exists(message_content):
                        # 读取文件内容用于下载
                        with open(message_content, "rb") as f:
                            file_data = f.read()
                        
                        # 创建下载按钮
                        st.markdown(f"""
                        <div class="message {message_class}">
                            <span class="username">{username}</span> 发送了一个文件:
                            <br>
                            <strong>{file_name}</strong>
                            <div class="file-info">大小: {file_size_str}</div>
                            <span class="timestamp">{timestamp}</span>
                        </div>
                        """, unsafe_allow_html=True)
                        
                        # 添加下载按钮
                        st.download_button(
                            label=f"📥 下载 {file_name}",
                            data=file_data,
                            file_name=file_name,
                            mime="application/octet-stream",
                            key=f"download_{msg_id}",
                            use_container_width=True
                        )
                    else:
                        st.markdown(f"""
                        <div class="message {message_class}">
                            <span class="username">{username}</span> 发送了一个文件:
                            <br>
                            <strong>{file_name}</strong>
                            <div class="file-info">大小: {file_size_str} (文件已丢失)</div>
                            <span class="timestamp">{timestamp}</span>
                        </div>
                        """, unsafe_allow_html=True)
            
            st.markdown("</div>", unsafe_allow_html=True)
        
        # 消息输入区域
        st.subheader("发送消息")
        
        # 选项卡:文本消息、图片、文件
        tab1, tab2, tab3 = st.tabs(["📝 文本消息", "🖼️ 发送图片", "📎 发送文件"])
        
        with tab1:
            # 文本消息输入
            if 'message_input' not in st.session_state:
                st.session_state.message_input = ""
            
            message_input = st.text_area(
                "输入消息:", 
                height=80, 
                max_chars=500, 
                value=st.session_state.message_input,
                key="message_input_widget"
            )
            
            if st.button("发送文本消息", use_container_width=True) and message_input:
                if len(message_input.strip()) > 0:
                    add_message(st.session_state.user_id, 'text', message_input.strip(), conn=conn)
                    st.session_state.message_input = ""
                    st.rerun()
        
        with tab2:
            # 图片上传
            uploaded_image = st.file_uploader(
                "选择图片文件", 
                type=['png', 'jpg', 'jpeg', 'gif', 'bmp'],
                key="image_uploader"
            )
            
            if uploaded_image is not None:
                # 显示预览
                image = Image.open(uploaded_image)
                st.image(image, caption="图片预览", use_column_width=True)
                
                if st.button("发送图片", use_container_width=True):
                    # 保存图片
                    file_path = save_uploaded_file(uploaded_image)
                    file_size = get_file_size(file_path)
                    
                    # 添加到消息
                    add_message(
                        st.session_state.user_id, 
                        'image', 
                        file_path, 
                        uploaded_image.name, 
                        file_size, 
                        conn=conn
                    )
                    st.rerun()
        
        with tab3:
            # 文件上传
            uploaded_file = st.file_uploader(
                "选择文件", 
                type=None,  # 允许所有文件类型
                key="file_uploader"
            )
            
            if uploaded_file is not None:
                file_size = len(uploaded_file.getvalue())
                file_size_str = format_file_size(file_size)
                
                st.write(f"**文件名:** {uploaded_file.name}")
                st.write(f"**文件大小:** {file_size_str}")
                
                if st.button("发送文件", use_container_width=True):
                    # 保存文件
                    file_path = save_uploaded_file(uploaded_file)
                    
                    # 添加到消息
                    add_message(
                        st.session_state.user_id, 
                        'file', 
                        file_path, 
                        uploaded_file.name, 
                        file_size, 
                        conn=conn
                    )
                    st.rerun()
        
        # 刷新按钮
        if st.button("刷新消息", use_container_width=True):
            st.rerun()
        
        # 自动刷新(每5秒)
        if time.time() - st.session_state.last_refresh > 5:
            st.session_state.last_refresh = time.time()
            st.rerun()

if __name__ == "__main__":
    main()

streamlit run app.py

聊天室应用

一个基于Python Streamlit和SQLite3的实时聊天室应用,支持文本、图片和文件传输。

功能特性

  • ✅ 用户注册/登录

  • ✅ 实时消息发送和接收

  • ✅ 图片上传和显示

  • ✅ 文件上传和下载

  • ✅ 消息历史记录

  • ✅ 自动刷新消息

  • ✅ 响应式界面设计

  • ✅ SQLite3数据库存储

  • ✅ 文件本地存储管理

安装和运行

1. 安装依赖

复制代码
pip install -r requirements.txt

streamlit>=1.28.0

Pillow>=10.0.0

2. 运行应用

复制代码
streamlit run app.py

3. 访问应用

打开浏览器访问 http://localhost:8501

新功能说明

图片上传功能

  • 支持格式:PNG、JPG、JPEG、GIF、BMP

  • 自动调整图片大小显示

  • 图片预览功能

  • 本地文件存储

文件上传功能

  • 支持所有文件类型

  • 显示文件大小信息

  • 文件信息展示

  • 本地文件存储管理

消息类型区分

  • 文本消息:绿色背景

  • 图片消息:浅绿色背景,带图片边框

  • 文件消息:橙色背景,显示文件信息

项目结构

复制代码
chatroom/
├── app.py              # 主应用文件
├── requirements.txt    # 项目依赖
├── chat.db            # SQLite数据库文件(自动创建)
└── README.md          # 项目说明

数据库设计

用户表 (users)

  • id: 用户ID(主键)

  • username: 用户名(唯一)

  • created_at: 创建时间

消息表 (messages)

  • id: 消息ID(主键)

  • user_id: 用户ID(外键)

  • message: 消息内容

  • timestamp: 发送时间

使用说明

  1. 登录/注册: 首次使用需要输入用户名,系统会自动创建用户

  2. 发送消息: 在底部输入框输入消息并点击"发送"

  3. 查看消息: 消息会自动显示在聊天区域,最新消息在底部

  4. 刷新消息: 点击"刷新"按钮或等待5秒自动刷新

  5. 退出: 点击"退出"按钮可以退出当前用户

技术栈

  • 前端: Streamlit

  • 后端: Python

  • 数据库: SQLite3

  • 实时更新: Streamlit自动刷新机制

注意事项

  • 应用支持多用户同时在线聊天

  • 消息历史最多显示50条最新消息

  • 用户名不能重复

  • 消息内容限制500字符以内

  • 应用会自动创建数据库文件

相关推荐
忘却的旋律dw18 分钟前
使用LLM模型的tokenizer报错AttributeError: ‘dict‘ object has no attribute ‘model_type‘
人工智能·pytorch·python
20岁30年经验的码农27 分钟前
Java RabbitMQ 实战指南
java·开发语言·python
studytosky2 小时前
深度学习理论与实战:MNIST 手写数字分类实战
人工智能·pytorch·python·深度学习·机器学习·分类·matplotlib
上不如老下不如小2 小时前
2025年第七届全国高校计算机能力挑战赛初赛 Python组 编程题汇总
开发语言·python·算法
Q_Q5110082853 小时前
python+django/flask的结合人脸识别和实名认证的校园论坛系统
spring boot·python·django·flask·node.js·php
Q_Q5110082853 小时前
python+django/flask的选课系统与课程评价整合系统
spring boot·python·django·flask·node.js·php
charlie1145141913 小时前
勇闯前后端Week2:后端基础——Flask API速览
笔记·后端·python·学习·flask·教程
豐儀麟阁贵3 小时前
8.2异常的抛出与捕捉
java·开发语言·python
interception3 小时前
爬虫js逆向,jsdom补环境,抖音,a_bogus
javascript·爬虫·python
林炳然3 小时前
Python-Basic Day-5 函数-生成器&装饰器
python