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

Flask 博客系统(Flask Blog System)

目标:零基础也能从头搭建一个支持文章管理、评论、分类标签、搜索、用户登录的博客系统
技术栈:Flask + SQLite + SQLAlchemy + Jinja2 + HTML/CSS + Flask-Login
开发工具:VSCode
学习重点:MVC 模式、数据库操作、会话管理、表单处理

一、项目概述

本项目是一个基于 Python Web 框架 Flask 构建的轻量级个人博客系统,旨在为开发者提供一个功能完整、结构清晰、易于学习和扩展的 Web 应用范例。系统采用 MVC 设计模式 组织代码结构,使用 SQLite 作为数据库,结合 HTML/CSS 前端模板 实现用户友好的交互界面,完整实现了博客核心功能模块。

该系统不仅具备实用价值,更注重教学意义,特别适合 Python Web 开发初学者理解 Web 请求响应机制、数据库操作、用户会话管理与前后端交互流程。

二、技术栈

类别技术
后端框架Flask (轻量级 Python Web 框架)
数据库SQLite(嵌入式数据库,无需额外服务)
ORMFlask-SQLAlchemy(对象关系映射,简化数据库操作)
用户认证Flask-Login(管理用户登录状态与会话)
前端技术HTML5 + CSS3 + Jinja2 模板引擎
安全机制密码哈希(Werkzeug.security)、CSRF 防护(基础)
开发工具VSCode、Python 虚拟环境

三、核心功能

  1. 文章管理

    • 支持文章的创建、编辑、删除与查看详情
    • 文章包含标题、内容、发布时间、作者信息
  2. 分类与标签系统

    • 每篇文章可归属一个分类(如“技术”、“生活”)
    • 支持多标签管理(如“Python”、“Flask”),便于内容组织与检索
  3. 用户评论功能

    • 登录用户可在文章页发表评论
    • 评论按时间排序展示,增强互动性
  4. 全文搜索

    • 支持通过关键词在文章标题和内容中进行模糊搜索
    • 搜索结果实时展示,提升用户体验
  5. 用户系统与会话管理

    • 用户注册与登录功能
    • 基于 Flask-Login 的会话管理,确保安全访问控制
    • 权限控制:仅文章作者可编辑或删除自己的文章
  6. 响应式前端界面

    • 使用原生 HTML/CSS 构建简洁美观的页面布局
    • 支持导航菜单、消息提示、表单验证等基础交互

四、架构设计(MVC 模式)

系统严格遵循 MVC(Model-View-Controller)设计模式,实现关注点分离:

  • Model(模型层):由 models.py 定义数据模型(User、Post、Comment、Category、Tag),通过 SQLAlchemy 映射到 SQLite 数据库。
  • View(视图层):使用 Jinja2 模板引擎在 templates/ 目录下渲染 HTML 页面,实现动态内容展示。
  • Controller(控制器层)routes.py 中的路由函数处理 HTTP 请求,调用模型进行数据操作,并返回对应视图。

五、项目特点

  • 零依赖外部服务:使用 SQLite,无需安装数据库服务器
  • 开箱即用:提供完整代码与依赖文件,一键运行
  • 学习友好:代码结构清晰,注释详尽,适合初学者理解 Web 开发全流程
  • 可扩展性强:模块化设计,便于后续集成 Markdown 编辑器、分页、REST API 等功能
  • 安全基础:用户密码加密存储,防止明文泄露

📁 项目目录结构

/blog├── app.py                 # 主程序入口├── models.py              # 数据模型定义├── routes.py              # 路由与控制器逻辑├── config.py              # 配置文件├── requirements.txt       # 依赖包列表├── instance/│   └── blog.db            # 自动生成的 SQLite 数据库├── templates/             # HTML 模板│   ├── base.html          # 布局模板│   ├── index.html         # 首页│   ├── login.html         # 登录页│   ├── create_post.html   # 发布文章│   ├── post.html          # 文章详情│   └── register.html      # 注册页(可选)└── static/└── style.css          # 样式文件

✅ 第一步:环境准备

1. 创建项目文件夹

mkdir blog && cd blog

2. 创建虚拟环境

python -m venv venv
# Windows
venv\Scripts\activate
# macOS/Linux
source venv/bin/activate

3. 安装依赖

创建 requirements.txt 文件:

Flask==3.0.3
Flask-SQLAlchemy==3.1.1
Flask-Login==0.6.3
Werkzeug==3.0.3

安装:

pip install -r requirements.txt

✅ 第二步:配置文件 config.py

# config.py
import osclass Config:SECRET_KEY = 'your-secret-key-here-change-it'  # 用于 session 加密SQLALCHEMY_DATABASE_URI = 'sqlite:///blog.db'SQLALCHEMY_TRACK_MODIFICATIONS = FalseDATABASE_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'instance', 'blog.db')

✅ 第三步:数据库模型 models.py

# models.py
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from datetime import datetimedb = SQLAlchemy()# 关联表:文章-标签(多对多)
post_tags = db.Table('post_tags',db.Column('post_id', db.Integer, db.ForeignKey('post.id')),db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
)class User(UserMixin, db.Model):id = db.Column(db.Integer, primary_key=True)username = db.Column(db.String(80), unique=True, nullable=False)password = db.Column(db.String(120), nullable=False)posts = db.relationship('Post', backref='author', lazy=True)comments = db.relationship('Comment', backref='author', lazy=True)class Category(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(50), unique=True, nullable=False)posts = db.relationship('Post', backref='category', lazy=True)class Tag(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(50), unique=True, nullable=False)class Post(db.Model):id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(120), nullable=False)content = db.Column(db.Text, nullable=False)created_at = db.Column(db.DateTime, default=datetime.utcnow)updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)category_id = db.Column(db.Integer, db.ForeignKey('category.id'))tags = db.relationship('Tag', secondary=post_tags, backref='posts')comments = db.relationship('Comment', backref='post', lazy=True)class Comment(db.Model):id = db.Column(db.Integer, primary_key=True)content = db.Column(db.Text, nullable=False)created_at = db.Column(db.DateTime, default=datetime.utcnow)post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

✅ 第四步:主程序 app.py

# app.py
from flask import Flask
from models import db
from routes import bp
from config import Config
from flask_login import LoginManagerdef create_app():app = Flask(__name__)app.config.from_object(Config)# 初始化数据库db.init_app(app)# 创建 instance 文件夹和数据库import osif not os.path.exists('instance'):os.makedirs('instance')with app.app_context():db.create_all()# 初始化登录管理login_manager = LoginManager()login_manager.login_view = 'bp.login'login_manager.init_app(app)from models import User@login_manager.user_loaderdef load_user(user_id):return User.query.get(int(user_id))# 注册蓝图app.register_blueprint(bp)return appif __name__ == '__main__':app = create_app()app.run(debug=True)

✅ 第五步:路由与控制器 routes.py

# routes.py
from flask import Blueprint, render_template, request, redirect, url_for, flash, session
from flask_login import login_user, logout_user, login_required, current_user
from models import db, User, Post, Category, Tag, Comment
from werkzeug.security import generate_password_hash, check_password_hashbp = Blueprint('bp', __name__)@bp.route('/')
def index():search = request.args.get('q')if search:posts = Post.query.filter((Post.title.contains(search)) | (Post.content.contains(search))).order_by(Post.created_at.desc()).all()else:posts = Post.query.order_by(Post.created_at.desc()).all()categories = Category.query.all()return render_template('index.html', posts=posts, categories=categories)@bp.route('/post/<int:id>')
def post(id):post = Post.query.get_or_404(id)return render_template('post.html', post=post)@bp.route('/post/create', methods=['GET', 'POST'])
@login_required
def create_post():if request.method == 'POST':title = request.form['title']content = request.form['content']category_id = request.form.get('category_id')tag_names = request.form.get('tags', '').split(',')# 获取或创建分类category = Noneif category_id:category = Category.query.get(category_id)# 处理标签tags = []for name in tag_names:name = name.strip()if name:tag = Tag.query.filter_by(name=name).first()if not tag:tag = Tag(name=name)db.session.add(tag)tags.append(tag)post = Post(title=title,content=content,category=category,tags=tags,author=current_user)db.session.add(post)db.session.commit()flash('文章发布成功!')return redirect(url_for('bp.index'))categories = Category.query.all()return render_template('create_post.html', categories=categories)@bp.route('/post/<int:id>/edit', methods=['GET', 'POST'])
@login_required
def edit_post(id):post = Post.query.get_or_404(id)if post.author != current_user:flash('你没有权限编辑此文章。')return redirect(url_for('bp.post', id=id))if request.method == 'POST':post.title = request.form['title']post.content = request.form['content']post.category_id = request.form.get('category_id')# 更新标签tag_names = request.form.get('tags', '').split(',')post.tags.clear()for name in tag_names:name = name.strip()if name:tag = Tag.query.filter_by(name=name).first()if not tag:tag = Tag(name=name)db.session.add(tag)post.tags.append(tag)db.session.commit()flash('文章已更新!')return redirect(url_for('bp.post', id=id))categories = Category.query.all()tag_str = ', '.join([t.name for t in post.tags])return render_template('create_post.html', post=post, categories=categories, tag_str=tag_str)@bp.route('/post/<int:id>/delete', methods=['POST'])
@login_required
def delete_post(id):post = Post.query.get_or_404(id)if post.author != current_user:flash('你没有权限删除此文章。')return redirect(url_for('bp.post', id=id))db.session.delete(post)db.session.commit()flash('文章已删除。')return redirect(url_for('bp.index'))@bp.route('/comment/<int:post_id>', methods=['POST'])
@login_required
def add_comment(post_id):content = request.form['content']post = Post.query.get_or_404(post_id)comment = Comment(content=content, post=post, author=current_user)db.session.add(comment)db.session.commit()flash('评论已发布!')return redirect(url_for('bp.post', id=post_id))@bp.route('/login', methods=['GET', 'POST'])
def login():if request.method == 'POST':username = request.form['username']password = request.form['password']user = User.query.filter_by(username=username).first()if user and check_password_hash(user.password, password):login_user(user)flash('登录成功!')return redirect(url_for('bp.index'))else:flash('用户名或密码错误。')return render_template('login.html')@bp.route('/register', methods=['GET', 'POST'])
def register():if request.method == 'POST':username = request.form['username']password = request.form['password']if User.query.filter_by(username=username).first():flash('用户名已存在。')else:hashed = generate_password_hash(password)user = User(username=username, password=hashed)db.session.add(user)db.session.commit()flash('注册成功,请登录。')return redirect(url_for('bp.login'))return render_template('register.html')@bp.route('/logout')
@login_required
def logout():logout_user()flash('已退出登录。')return redirect(url_for('bp.index'))

✅ 第六步:HTML 模板

1. 基础布局 templates/base.html

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{% block title %}我的博客{% endblock %}</title><link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body><header><h1><a href="{{ url_for('bp.index') }}">我的博客</a></h1><nav>{% if current_user.is_authenticated %}<span>欢迎, {{ current_user.username }}!</span><a href="{{ url_for('bp.create_post') }}">发布文章</a><a href="{{ url_for('bp.logout') }}">退出</a>{% else %}<a href="{{ url_for('bp.login') }}">登录</a><a href="{{ url_for('bp.register') }}">注册</a>{% endif %}</nav></header><main>{% with messages = get_flashed_messages() %}{% if messages %}<ul class="flashes">{% for message in messages %}<li>{{ message }}</li>{% endfor %}</ul>{% endif %}{% endwith %}{% block content %}{% endblock %}</main><footer><p>&copy; 2025 我的博客系统</p></footer>
</body>
</html>

2. 首页 templates/index.html

<!-- templates/index.html -->
{% extends "base.html" %}{% block content %}
<h2>文章列表</h2><!-- 搜索框 -->
<form method="get" class="search-form"><input type="text" name="q" placeholder="搜索文章..." value="{{ request.args.get('q', '') }}"><button type="submit">搜索</button>
</form><!-- 文章列表 -->
{% for post in posts %}
<article class="post-preview"><h3><a href="{{ url_for('bp.post', id=post.id) }}">{{ post.title }}</a></h3><p class="meta">发布于 {{ post.created_at.strftime('%Y-%m-%d %H:%M') }}{% if post.category %} | 分类: {{ post.category.name }}{% endif %}</p><p>{{ post.content[:200] }}...</p>{% if post.tags %}<div class="tags">{% for tag in post.tags %}<span class="tag">{{ tag.name }}</span>{% endfor %}</div>{% endif %}
</article>
{% else %}
<p>暂无文章。</p>
{% endfor %}
{% endblock %}

3. 文章详情 templates/post.html

<!-- templates/post.html -->
{% extends "base.html" %}{% block content %}
<article class="post"><h1>{{ post.title }}</h1><p class="meta">作者: {{ post.author.username }} |发布于 {{ post.created_at.strftime('%Y-%m-%d %H:%M') }}{% if post.category %} | 分类: {{ post.category.name }}{% endif %}</p><div class="content">{{ post.content }}</div>{% if post.tags %}<div class="tags">{% for tag in post.tags %}<span class="tag">{{ tag.name }}</span>{% endfor %}</div>{% endif %}<!-- 编辑/删除 -->{% if current_user.is_authenticated and current_user == post.author %}<p><a href="{{ url_for('bp.edit_post', id=post.id) }}">编辑</a> |<a href="#" onclick="if(confirm('确定删除?')) document.getElementById('delete-form').submit()">删除</a></p><form id="delete-form" action="{{ url_for('bp.delete_post', id=post.id) }}" method="post" style="display:none;"></form>{% endif %}<!-- 评论 --><h3>评论 ({{ post.comments|length }})</h3>{% if current_user.is_authenticated %}<form method="post" action="{{ url_for('bp.add_comment', post_id=post.id) }}"><textarea name="content" placeholder="写下你的评论..." required></textarea><button type="submit">发表评论</button></form>{% else %}<p><a href="{{ url_for('bp.login') }}">登录</a>后可发表评论。</p>{% endif %}{% for comment in post.comments %}<div class="comment"><strong>{{ comment.author.username }}</strong><span class="date">{{ comment.created_at.strftime('%Y-%m-%d %H:%M') }}</span><p>{{ comment.content }}</p></div>{% endfor %}
</article>
{% endblock %}

4. 发布/编辑文章 templates/create_post.html

<!-- templates/create_post.html -->
{% extends "base.html" %}{% block content %}
<h2>{% if post %}编辑文章{% else %}发布新文章{% endif %}</h2><form method="post"><label>标题 *</label><input type="text" name="title" value="{{ post.title if post }}" required><label>内容 *</label><textarea name="content" rows="10" required>{{ post.content if post }}</textarea><label>分类</label><select name="category_id"><option value="">无分类</option>{% for cat in categories %}<option value="{{ cat.id }}" {% if post and post.category_id == cat.id %}selected{% endif %}>{{ cat.name }}</option>{% endfor %}</select><label>标签(多个用逗号分隔)</label><input type="text" name="tags" value="{{ tag_str if tag_str else '' }}" placeholder="如:Python,Flask"><button type="submit">{% if post %}更新文章{% else %}发布文章{% endif %}</button>
</form><a href="{{ url_for('bp.index') }}">返回首页</a>
{% endblock %}

6. 注册页 templates/register.html

<!-- templates/register.html -->
{% extends "base.html" %}{% block content %}
<h2>注册</h2>
<form method="post"><label>用户名 *</label><input type="text" name="username" required><label>密码 *</label><input type="password" name="password" required><button type="submit">注册</button>
</form>
<p>已有账号?<a href="{{ url_for('bp.login') }}">去登录</a></p>
{% endblock %}

✅ 第七步:CSS 样式 static/style.css

/* static/style.css */
* {margin: 0;padding: 0;box-sizing: border-box;
}body {font-family: Arial, sans-serif;line-height: 1.6;color: #333;max-width: 800px;margin: 0 auto;padding: 20px;
}header {display: flex;justify-content: space-between;align-items: center;padding-bottom: 20px;border-bottom: 1px solid #eee;margin-bottom: 30px;
}header h1 a {text-decoration: none;color: #0056b3;
}nav a {margin-left: 15px;color: #0056b3;text-decoration: none;
}nav a:hover {text-decoration: underline;
}.flashes {background: #d4edda;color: #155724;padding: 10px;border-radius: 4px;margin-bottom: 20px;
}.post-preview {margin-bottom: 30px;padding-bottom: 20px;border-bottom: 1px solid #eee;
}.post-preview h3 {margin-bottom: 5px;
}.post-preview h3 a {color: #0056b3;text-decoration: none;
}.meta {color: #666;font-size: 0.9em;margin-bottom: 10px;
}.tags {margin-top: 10px;
}.tag {display: inline-block;background: #0056b3;color: white;padding: 2px 8px;border-radius: 12px;font-size: 0.8em;margin-right: 5px;
}form {margin: 20px 0;
}label {display: block;margin: 10px 0 5px;font-weight: bold;
}input[type="text"], input[type="password"], textarea, select {width: 100%;padding: 8px;border: 1px solid #ddd;border-radius: 4px;
}button {background: #0056b3;color: white;padding: 10px 15px;border: none;border-radius: 4px;cursor: pointer;margin-top: 10px;
}button:hover {background: #003d82;
}.search-form {display: flex;margin-bottom: 30px;
}.search-form input {flex: 1;margin-right: 10px;
}.comment {border: 1px solid #eee;padding: 10px;margin-bottom: 10px;border-radius: 4px;
}.comment .date {color: #666;font-size: 0.8em;margin-left: 10px;
}footer {text-align: center;margin-top: 50px;color: #666;font-size: 0.9em;
}

✅ 第八步:运行项目

  1. 确保你在 blog 目录下
  2. 运行:
python app.py

浏览器访问:http://127.0.0.1:5000

✅ 第九步:使用说明

  1. 访问 /register 注册一个账号
  2. 登录后即可发布文章
  3. 支持分类、标签、搜索、评论
  4. 只有作者可编辑/删除自己的文章

✅ 学习要点总结

概念项目中体现
MVCmodels.py(M) + routes.py(C) + templates/(V)
数据库操作SQLAlchemy ORM 实现增删改查
会话管理Flask-Login 处理登录状态
表单处理request.form 获取数据
模板渲染Jinja2 动态生成 HTML

✅ 后续建议

  • 添加 Markdown 支持
  • 增加分页
  • 使用 Bootstrap 美化界面
  • 部署到云端(如 Render.com)


文章转载自:

http://y2YDJJfv.rrjzp.cn
http://2XWsIaEA.rrjzp.cn
http://K1JmTZXe.rrjzp.cn
http://D4pYYCIX.rrjzp.cn
http://cwCF8XPc.rrjzp.cn
http://b1yJtw7I.rrjzp.cn
http://Rf7M452p.rrjzp.cn
http://XDPjzhup.rrjzp.cn
http://yj34rxmf.rrjzp.cn
http://zphvbe5t.rrjzp.cn
http://jFrIECbj.rrjzp.cn
http://dlW3x21U.rrjzp.cn
http://BK8RtRpw.rrjzp.cn
http://A9g7TMhq.rrjzp.cn
http://xQp1PAoL.rrjzp.cn
http://dlYaF89z.rrjzp.cn
http://zd5niySA.rrjzp.cn
http://7EbSnsuy.rrjzp.cn
http://qc9FNv1o.rrjzp.cn
http://J7YsSkiG.rrjzp.cn
http://6zCEtC1L.rrjzp.cn
http://iTy93p3G.rrjzp.cn
http://VAyHMMLH.rrjzp.cn
http://NVKRgEKQ.rrjzp.cn
http://u93nMvyV.rrjzp.cn
http://ao3D7wdQ.rrjzp.cn
http://aaulaygc.rrjzp.cn
http://ckZB0sH7.rrjzp.cn
http://NhPun2Jm.rrjzp.cn
http://bnxb6bfr.rrjzp.cn
http://www.dtcms.com/a/374243.html

相关文章:

  • Qt_UI界面的设计
  • pycharm 最新版上一次编辑位置
  • 【Pywinauto库】1. 3 Inspect.exe 使用详解指南
  • 「日拱一码」083 深度学习——残差网络
  • 注意力模块改进方法的原理及实现(MHA、MQA、GQA、MLA)
  • 蚂蚁 S21 Pro 220T矿机参数详解:SHA-256算法高效算力分析
  • 大模型测试包含哪些方面
  • 基于R语言的物种气候生态位动态量化与分布特征模拟
  • NGUI--Anchor组件和 事件系统
  • 基于Django的“酒店推荐系统”设计与开发(源码+数据库+文档+PPT)
  • OpenLayers数据源集成 -- 章节一:图像图层详解
  • 深度学习架构的硬件共生论:为什么GPU决定了AI的进化方向(Transformer、SSM、Mamba、MoE、CNN是什么、对比表格)
  • AndroidWorld+mobileRL
  • langchain4j笔记篇(阳哥)
  • 精简删除WIN11.24H2企业版映像内的OneDrive安装程序方法,卸载OneDrive组件
  • spring指南学习随记(一)
  • 安装配置简易VM虚拟机(CentOS 7)
  • 虚拟机中centos简单配置
  • commons-logging
  • 【小宁学习日记6 PCB】电路原理图
  • Rust位置表达式和值表达式
  • 对比:ClickHouse/MySQL/Apache Doris
  • 2025年学英语学习机选购指南
  • 浪涌测试主要用于评估电子设备或元器件在遭受短时高强度电压 / 电流冲击(浪涌)时的耐受能力
  • ANDROID,Jetpack Compose, 贪吃蛇小游戏Demo
  • html中列表和表格的使用
  • MyBatis-Plus 深度解析:IService 接口全指南
  • iPaaS 如何帮助 CIO 减少 50% 的集成成本?
  • [运动控制]PID算法再深入--多环组合控制
  • llm的一点学习笔记