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

好的!我来为你设计一个Python + MySQL 实现的迷你博客评论系统,非常适合新手学习和练手。这个系统包含文章展示和评论功能,采用清晰的 MVC 架构。
系统功能概述
核心功能
-
文章浏览:查看文章列表和详情
-
评论管理:对文章发表评论、查看评论
-
基础管理:添加新文章
技术栈
-
后端:Flask (Python Web框架)
-
数据库:MySQL
-
前端:Bootstrap 5 + Jinja2模板
-
连接方式:PyMySQL
环境准备
1. 安装依赖
pip install flask pymysql
2. 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.py
import 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, params=None):
"""查询多条记录"""
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, params=None):
"""查询单条记录"""
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, params=None):
"""执行增删改操作"""
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.py
from 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', articles=articles)
@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', article=article, comments=comments)
@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_id=article_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_id=article_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_id=comment['article_id']))
flash('评论不存在', 'danger')
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True, port=5000)
HTML 模板
templates/base.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}迷你博客{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">📝 迷你博客</a>
<div class="collapse navbar-collapse">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="/">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/new_article">写文章</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- Flash消息 -->
<div class="container mt-3">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<!-- 主要内容 -->
<div class="container mt-4">
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
templates/index.html
{% extends 'base.html' %}
{% block title %}文章列表{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>📚 文章列表</h2>
<a href="/new_article" class="btn btn-primary">✏️ 写新文章</a>
</div>
{% if articles %}
<div class="row">
{% for article in articles %}
<div class="col-md-6 mb-4">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="card-title">{{ article.title }}</h5>
<p class="card-text text-muted">
<small>作者:{{ article.author }} | {{ article.created_at.strftime('%Y-%m-%d %H:%M') }}</small>
</p>
<p class="card-text">{{ article.content[:150] }}{% if article.content|length > 150 %}...{% endif %}</p>
<a href="/article/{{ article.id }}" class="btn btn-outline-primary">阅读全文 →</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="alert alert-info">暂无文章,快来发布第一篇吧!</div>
{% endif %}
{% endblock %}
templates/article.html
{% extends 'base.html' %}
{% block title %}{{ article.title }}{% endblock %}
{% block content %}
<!-- 文章内容 -->
<div class="card mb-4">
<div class="card-header">
<h3>{{ article.title }}</h3>
<small class="text-muted">
作者:{{ article.author }} | 发布于:{{ article.created_at.strftime('%Y-%m-%d %H:%M') }}
</small>
</div>
<div class="card-body">
<p class="card-text" style="white-space: pre-wrap;">{{ article.content }}</p>
</div>
</div>
<!-- 评论区 -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">💬 评论 ({{ comments|length }})</h5>
</div>
<div class="card-body">
<!-- 评论表单 -->
<form action="/post_comment/{{ article.id }}" method="POST" class="mb-4">
<div class="mb-3">
<input type="text" name="username" class="form-control" placeholder="你的昵称" required>
</div>
<div class="mb-3">
<input type="email" name="email" class="form-control" placeholder="邮箱(选填)">
</div>
<div class="mb-3">
<textarea name="content" class="form-control" rows="3" placeholder="写下你的评论..." required></textarea>
</div>
<button type="submit" class="btn btn-primary">发表评论</button>
</form>
<hr>
<!-- 评论列表 -->
{% if comments %}
{% for comment in comments %}
<div class="border-bottom mb-3 pb-3">
<div class="d-flex justify-content-between">
<strong>{{ comment.username }}</strong>
<small class="text-muted">{{ comment.created_at.strftime('%Y-%m-%d %H:%M') }}</small>
</div>
{% if comment.email %}
<small class="text-muted">{{ comment.email }}</small>
{% endif %}
<p class="mt-2 mb-1">{{ comment.content }}</p>
<a href="/delete_comment/{{ comment.id }}" class="text-danger small"
onclick="return confirm('确定删除这条评论?')">删除</a>
</div>
{% endfor %}
{% else %}
<p class="text-muted">暂无评论,快来抢沙发吧!</p>
{% endif %}
</div>
</div>
<div class="mt-3">
<a href="/" class="btn btn-secondary">← 返回首页</a>
</div>
{% endblock %}
templates/new_article.html
{% extends 'base.html' %}
{% block title %}发布文章{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow">
<div class="card-header">
<h4>✏️ 发布新文章</h4>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label class="form-label">文章标题</label>
<input type="text" name="title" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">作者</label>
<input type="text" name="author" class="form-control" placeholder="默认为匿名">
</div>
<div class="mb-3">
<label class="form-label">文章内容</label>
<textarea name="content" class="form-control" rows="10" required></textarea>
</div>
<button type="submit" class="btn btn-primary">发布文章</button>
<a href="/" class="btn 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中设置了 charset='utf8mb4'
❌ 端口被占用
修改 app.py最后一行:
app.run(debug=True, port=5001) # 改用其他端口
进阶扩展建议
当你熟悉基础功能后,可以尝试添加:
-
用户注册登录:使用 session 或 Flask-Login
-
评论点赞/踩
-
富文本编辑器:集成 CKEditor 或 TinyMCE
-
分页功能:文章和评论分页显示
-
搜索功能:按标题或内容搜索文章
-
API接口:提供 RESTful API 供移动端调用
更多推荐
所有评论(0)