好的!我来为你设计一个Python + MySQL 实现的迷你博客评论系统,非常适合新手学习和练手。这个系统包含文章展示和评论功能,采用清晰的 MVC 架构。


系统功能概述

核心功能

  1. 文章浏览:查看文章列表和详情

  2. 评论管理:对文章发表评论、查看评论

  3. 基础管理:添加新文章

技术栈

  • 后端: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 %}

运行步骤

  1. 确保 MySQL 服务已启动

  2. 修改配置:编辑 config.py,将 password改为你的 MySQL 密码

  3. 初始化数据库:执行前面提供的 SQL 语句

  4. 运行应用

python app.py
  1. 访问系统:浏览器打开 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)  # 改用其他端口

进阶扩展建议

当你熟悉基础功能后,可以尝试添加:

  1. 用户注册登录:使用 session 或 Flask-Login

  2. 评论点赞/踩

  3. 富文本编辑器:集成 CKEditor 或 TinyMCE

  4. 分页功能:文章和评论分页显示

  5. 搜索功能:按标题或内容搜索文章

  6. API接口:提供 RESTful API 供移动端调用

更多推荐