Python + MySQL 实现的迷你博客评论系统

📅 2026/6/30 4:41:00
Python + MySQL 实现的迷你博客评论系统
好的我来为你设计一个Python MySQL 实现的迷你博客评论系统非常适合新手学习和练手。这个系统包含文章展示和评论功能采用清晰的 MVC 架构。系统功能概述核心功能文章浏览查看文章列表和详情评论管理对文章发表评论、查看评论基础管理添加新文章技术栈后端Flask (Python Web框架)数据库MySQL前端Bootstrap 5 Jinja2模板连接方式PyMySQL环境准备1. 安装依赖pip install flask pymysql2. MySQL 数据库准备登录 MySQL 后执行CREATE DATABASE blog_comment_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE blog_comment_system; -- 文章表 CREATE TABLE articles ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, content TEXT NOT NULL, author VARCHAR(50) DEFAULT 匿名, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 评论表 CREATE TABLE comments ( id INT AUTO_INCREMENT PRIMARY KEY, article_id INT NOT NULL, username VARCHAR(50) NOT NULL, email VARCHAR(100), content TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE ); -- 插入测试数据 INSERT INTO articles (title, content, author) VALUES (Python入门指南, Python是一种简单易学的编程语言适合初学者入门..., 小明), (MySQL学习笔记, MySQL是最流行的开源关系型数据库之一..., 小红); INSERT INTO comments (article_id, username, content) VALUES (1, 张三, 写得很棒对我帮助很大), (1, 李四, 请问有推荐的教材吗), (2, 王五, 学到了很多感谢分享);项目结构blog_comment_system/ ├── app.py # 主程序 ├── config.py # 配置文件 ├── models.py # 数据库操作 ├── templates/ │ ├── base.html # 基础模板 │ ├── index.html # 文章列表 │ ├── article.html # 文章详情评论 │ └── new_article.html # 发布文章 └── static/ └── style.css # 自定义样式可选完整代码实现1. 配置文件config.py# 数据库配置 DB_CONFIG { host: localhost, port: 3306, user: root, password: your_password, # 改为你自己的密码 database: blog_comment_system, charset: utf8mb4 }2. 数据库操作层models.pyimport pymysql from config import DB_CONFIG class Database: def __init__(self): self.connection None def connect(self): 建立数据库连接 if not self.connection: self.connection pymysql.connect(**DB_CONFIG) return self.connection def close(self): 关闭连接 if self.connection: self.connection.close() self.connection None def query_all(self, sql, paramsNone): 查询多条记录 conn self.connect() try: with conn.cursor(pymysql.cursors.DictCursor) as cursor: cursor.execute(sql, params or ()) return cursor.fetchall() finally: self.close() def query_one(self, sql, paramsNone): 查询单条记录 conn self.connect() try: with conn.cursor(pymysql.cursors.DictCursor) as cursor: cursor.execute(sql, params or ()) return cursor.fetchone() finally: self.close() def execute(self, sql, paramsNone): 执行增删改操作 conn self.connect() try: with conn.cursor() as cursor: affected_rows cursor.execute(sql, params or ()) conn.commit() return affected_rows except Exception as e: conn.rollback() raise e finally: self.close() db Database()3. 主应用app.pyfrom flask import Flask, render_template, request, redirect, url_for, flash from models import db app Flask(__name__) app.secret_key your-secret-key-here # 用于flash消息 app.route(/) def index(): 文章列表页 articles db.query_all(SELECT * FROM articles ORDER BY created_at DESC) return render_template(index.html, articlesarticles) app.route(/article/int:article_id) def article_detail(article_id): 文章详情页包含评论 # 获取文章 article db.query_one(SELECT * FROM articles WHERE id %s, (article_id,)) if not article: flash(文章不存在, danger) return redirect(url_for(index)) # 获取评论 comments db.query_all( SELECT * FROM comments WHERE article_id %s ORDER BY created_at DESC, (article_id,) ) return render_template(article.html, articlearticle, commentscomments) app.route(/new_article, methods[GET, POST]) def new_article(): 发布新文章 if request.method POST: title request.form.get(title, ).strip() content request.form.get(content, ).strip() author request.form.get(author, 匿名).strip() if not title or not content: flash(标题和内容不能为空, warning) return render_template(new_article.html) try: db.execute( INSERT INTO articles (title, content, author) VALUES (%s, %s, %s), (title, content, author) ) flash(文章发布成功, success) return redirect(url_for(index)) except Exception as e: flash(f发布失败{str(e)}, danger) return render_template(new_article.html) app.route(/post_comment/int:article_id, methods[POST]) def post_comment(article_id): 提交评论 # 验证文章是否存在 article db.query_one(SELECT id FROM articles WHERE id %s, (article_id,)) if not article: flash(文章不存在, danger) return redirect(url_for(index)) username request.form.get(username, ).strip() email request.form.get(email, ).strip() content request.form.get(content, ).strip() if not username or not content: flash(用户名和评论内容不能为空, warning) return redirect(url_for(article_detail, article_idarticle_id)) try: db.execute( INSERT INTO comments (article_id, username, email, content) VALUES (%s, %s, %s, %s), (article_id, username, email, content) ) flash(评论发表成功, success) except Exception as e: flash(f评论失败{str(e)}, danger) return redirect(url_for(article_detail, article_idarticle_id)) app.route(/delete_comment/int:comment_id) def delete_comment(comment_id): 删除评论简单演示用 comment db.query_one(SELECT article_id FROM comments WHERE id %s, (comment_id,)) if comment: db.execute(DELETE FROM comments WHERE id %s, (comment_id,)) flash(评论已删除, info) return redirect(url_for(article_detail, article_idcomment[article_id])) flash(评论不存在, danger) return redirect(url_for(index)) if __name__ __main__: app.run(debugTrue, port5000)HTML 模板templates/base.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{% block title %}迷你博客{% endblock %}/title link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/css/bootstrap.min.css relstylesheet /head body !-- 导航栏 -- nav classnavbar navbar-expand-lg navbar-dark bg-primary div classcontainer a classnavbar-brand href/ 迷你博客/a div classcollapse navbar-collapse ul classnavbar-nav ms-auto li classnav-item a classnav-link href/首页/a /li li classnav-item a classnav-link href/new_article写文章/a /li /ul /div /div /nav !-- Flash消息 -- div classcontainer mt-3 {% with messages get_flashed_messages(with_categoriestrue) %} {% if messages %} {% for category, message in messages %} div classalert alert-{{ category }} alert-dismissible fade show {{ message }} button typebutton classbtn-close data-bs-dismissalert/button /div {% endfor %} {% endif %} {% endwith %} /div !-- 主要内容 -- div classcontainer mt-4 {% block content %}{% endblock %} /div script srchttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/js/bootstrap.bundle.min.js/script /body /htmltemplates/index.html{% extends base.html %} {% block title %}文章列表{% endblock %} {% block content %} div classd-flex justify-content-between align-items-center mb-4 h2 文章列表/h2 a href/new_article classbtn btn-primary✏️ 写新文章/a /div {% if articles %} div classrow {% for article in articles %} div classcol-md-6 mb-4 div classcard h-100 shadow-sm div classcard-body h5 classcard-title{{ article.title }}/h5 p classcard-text text-muted small作者{{ article.author }} | {{ article.created_at.strftime(%Y-%m-%d %H:%M) }}/small /p p classcard-text{{ article.content[:150] }}{% if article.content|length 150 %}...{% endif %}/p a href/article/{{ article.id }} classbtn btn-outline-primary阅读全文 →/a /div /div /div {% endfor %} /div {% else %} div classalert alert-info暂无文章快来发布第一篇吧/div {% endif %} {% endblock %}templates/article.html{% extends base.html %} {% block title %}{{ article.title }}{% endblock %} {% block content %} !-- 文章内容 -- div classcard mb-4 div classcard-header h3{{ article.title }}/h3 small classtext-muted 作者{{ article.author }} | 发布于{{ article.created_at.strftime(%Y-%m-%d %H:%M) }} /small /div div classcard-body p classcard-text stylewhite-space: pre-wrap;{{ article.content }}/p /div /div !-- 评论区 -- div classcard div classcard-header d-flex justify-content-between align-items-center h5 classmb-0 评论 ({{ comments|length }})/h5 /div div classcard-body !-- 评论表单 -- form action/post_comment/{{ article.id }} methodPOST classmb-4 div classmb-3 input typetext nameusername classform-control placeholder你的昵称 required /div div classmb-3 input typeemail nameemail classform-control placeholder邮箱选填 /div div classmb-3 textarea namecontent classform-control rows3 placeholder写下你的评论... required/textarea /div button typesubmit classbtn btn-primary发表评论/button /form hr !-- 评论列表 -- {% if comments %} {% for comment in comments %} div classborder-bottom mb-3 pb-3 div classd-flex justify-content-between strong{{ comment.username }}/strong small classtext-muted{{ comment.created_at.strftime(%Y-%m-%d %H:%M) }}/small /div {% if comment.email %} small classtext-muted{{ comment.email }}/small {% endif %} p classmt-2 mb-1{{ comment.content }}/p a href/delete_comment/{{ comment.id }} classtext-danger small onclickreturn confirm(确定删除这条评论)删除/a /div {% endfor %} {% else %} p classtext-muted暂无评论快来抢沙发吧/p {% endif %} /div /div div classmt-3 a href/ classbtn btn-secondary← 返回首页/a /div {% endblock %}templates/new_article.html{% extends base.html %} {% block title %}发布文章{% endblock %} {% block content %} div classrow justify-content-center div classcol-md-8 div classcard shadow div classcard-header h4✏️ 发布新文章/h4 /div div classcard-body form methodPOST div classmb-3 label classform-label文章标题/label input typetext nametitle classform-control required /div div classmb-3 label classform-label作者/label input typetext nameauthor classform-control placeholder默认为匿名 /div div classmb-3 label classform-label文章内容/label textarea namecontent classform-control rows10 required/textarea /div button typesubmit classbtn btn-primary发布文章/button a href/ classbtn btn-secondary取消/a /form /div /div /div /div {% endblock %}运行步骤确保 MySQL 服务已启动修改配置编辑config.py将password改为你的 MySQL 密码初始化数据库执行前面提供的 SQL 语句运行应用python app.py访问系统浏览器打开http://127.0.0.1:5000新手常见问题解决❌ 连接 MySQL 报错pip install cryptography # 有时需要这个库或者检查 MySQL 服务是否启动# Windows net start mysql # Mac/Linux sudo systemctl start mysql❌ 中文乱码确保数据库创建时指定了utf8mb4并且在config.py中设置了charsetutf8mb4❌ 端口被占用修改app.py最后一行app.run(debugTrue, port5001) # 改用其他端口进阶扩展建议当你熟悉基础功能后可以尝试添加用户注册登录使用 session 或 Flask-Login评论点赞/踩富文本编辑器集成 CKEditor 或 TinyMCE分页功能文章和评论分页显示搜索功能按标题或内容搜索文章API接口提供 RESTful API 供移动端调用