资讯动态

Flask博客系统开发:框架选型、数据库设计与核心功能实现

发布时间:2026/9/16 17:04:45 来源:尧图企业网站定制
简介面向 Python Web 毕业设计场景基于 Flask 的博客系统完整项目适合毕业设计或课程设计学生参考与复用。项目已获导师指导并以高分通过围绕用户管理、标签管理、分类管理、文章管理、公告资讯管理、举报信息管理、消息通知管理与系统管理等核心模块展开前后端分工明确。压缩包共五百七十一个文件、二十一点四八 MB含四十三个 Python 后端源码、一百一十一个 Vue 组件、JS/CSS 前端资源、SQL 数据库脚本、开发文档和启动脚本图片与 SVG 图标用于页面展示目录结构分后端、前端、脚本和文档便于定位。目前已有 135 人下载学习。除完整源码和数据库外还附带运行教程与逻辑讲解有助于理解 Flask 与 MySQL 的接口实现、Vue 页面与后端联动、权限控制和消息通知机制开发文档覆盖需求分析、数据库设计、接口设计、部署配置便于二次开发和毕业答辩。下载后可直接运行作为课设提交、毕设展演或 Flask 框架学习的实用素材。1. 基于Flask的博客系统这个毕业设计题目到底在考察什么看到「Python毕业设计-基于Flask的博客系统(源码数据库开发文档).zip」这类资源包很多人第一反应是「这题太老了」。但换个角度能把源码、数据库、开发文档打包成一套三件套的题目恰恰是Flask方向最典型的毕业设计形态它覆盖了Python Web开发的完整闭环。从安装Python环境、把Flask框架跑起来到建表、写路由、渲染模板再到把项目整理成开发文档交付每一步都有明确的产出物。这套题适合三类人急着在短时间内完成作品并准备论文的同学、刚学完Python基础想用Flask框架练手的开发者以及需要一套能现场演示的python源码项目去应对面试的初级程序员。选这个题等于选了一条完成度可以被看见的路线。2. Flask博客系统的技术选型与工程骨架搭建2.1 Flask框架优于Django和FastAPI的毕业设计理由先解决选型。博客系统的核心业务就是用户登录、文章分类、评论这些数据库增删改查操作数据量和并发都不高技术栈的每一项都可以展开讲清楚。Django自带Admin后台和完整ORM开发速度确实快但答辩时容易陷入「框架替我做了大部分事」的被动评审问到底层机制时很难接住。FastAPI的异步特性和类型校验在这个项目里没有非用不可的场景反而要把async/await、协程模型讲透才不露怯。Flask框架的定位是微框架路由由你注册ORM由Flask-SQLAlchemy接入登录状态用session自己管理每一层都暴露在代码里。评审老师问「请求在Flask框架里是怎么走的」你可以从WSGI服务器讲到视图函数返回值这是选型时最能体现理解深度的地方。第二个理由和开发文档强相关。毕业设计的开发文档通常要求包含需求分析、系统设计、数据库设计、接口说明和测试结果Flask项目的模块边界清晰文档章节可以直接对应到蓝图和模型文件。Django的「约定优于配置」写出来的文档容易变成框架说明书。所以这类资源包里出现Flask不是因为Flask比Django简单而是因为它能被完整地讲清楚。横向对比可以浓缩成一张表维度DjangoFlaskFastAPI学习曲线陡概念多平缓按需引入中等需懂异步答辩可讲深度框架封装多各层可见需讲清楚协程文档章节对应一般高中等2.2 用app工厂模式搭建Flask博客的最小目录结构既然选了Flask框架工程结构就要按可扩展的方式组织。常见做法是把项目拆成入口、配置、应用包和测试目录我一般会这样建blog_system/ ├── run.py # 应用入口python run.py 启动 ├── config.py # 开发/测试/生产三套配置 ├── requirements.txt # 依赖清单 ├── app/ │ ├── __init__.py # create_app 工厂函数 │ ├── models.py # SQLAlchemy 模型 │ ├── auth/ # 认证蓝图 │ │ ├── __init__.py │ │ └── views.py │ ├── blog/ # 文章蓝图 │ │ ├── __init__.py │ │ └── views.py │ └── templates/ │ ├── base.html │ └── index.html └── tests/ └── test_blog.pyapp/init.py里的工厂函数是项目地基写法如下from flask import Flask from flask_sqlalchemy import SQLAlchemy from config import Config db SQLAlchemy() def create_app(config_classConfig): app Flask(__name__) app.config.from_object(config_class) db.init_app(app) from app.auth import bp as auth_bp from app.blog import bp as blog_bp app.register_blueprint(auth_bp, url_prefix/auth) app.register_blueprint(blog_bp) with app.app_context(): db.create_all() return app三个参数值得展开。config_class是可替换的配置类测试时传入testing配置SQLALCHEMY_DATABASE_URI就能指向内存SQLite业务代码不用改一行。url_prefix/auth给认证蓝图的所有路由自动加前缀login视图的完整URL变成/auth/login文章模块的路由因此可以保持一级路径。db.create_all()必须在app_context里调用因为SQLAlchemy的元数据需要应用上下文才能绑定数据库但它只能建新表、不能改旧表正式开发要用迁移工具。requirements.txt里锁定flask、flask-sqlalchemy、flask-migrate三个核心依赖的版本号避免换机器后跑不起来。2.3 蓝图Blueprint拆分认证与文章模块的注册细节蓝图解决的是Flask框架最常见的痛点单文件路由越写越长。认证相关路由放进app/auth/文章相关放进app/blog/每个蓝图在自己的__init__.py里创建并导入from flask import Blueprint bp Blueprint(auth, __name__) from app.auth import views # 必须放在bp创建之后导入顺序有坑views.py里的bp.route装饰器引用bp对象如果from import放在文件顶部导入views时bp还是None启动直接AttributeError。把导入语句放到蓝图创建之后是官方也认可的标准写法。开发文档画系统架构时直接按「请求进入Flask框架后分发到哪个蓝图」来画每条分发线对应一个url_prefix评审看到这里基本不会再往深问。提示config.py里至少要声明SECRET_KEY和SQLALCHEMY_DATABASE_URI两个配置项前者不设置session功能会直接运行报错。3. 博客系统的数据库设计从表结构到SQLAlchemy模型落地3.1 博客系统四张核心表用户、文章、分类、评论博客系统的数据关系不复杂但数据库设计的评审标准是「范式正确、关系完整」。最少需要四张表表名关键字段与其他表的关系userid, username, password_hash一对多一个用户写多篇文章categoryid, name一对多一个分类下多篇文章postid, title, body, created_at, user_id, category_id多对一文章归属用户与分类commentid, body, created_at, post_id, user_id多对一评论归属文章与用户password_hash字段存的是哈希值而不是明文密码这是答辩必问点。werkzeug的generate_password_hash默认使用pbkdf2算法加盐即使数据库泄露也不能直接反推出密码。外键user_id和category_id用db.ForeignKey声明配合relationship让ORM直接通过post.author拿到用户对象省去手写联表查询。四张表刚好覆盖一对多和多对一两类关系足够展示对数据库课程设计里「关系建模」的理解。3.2 用Flask-SQLAlchemy定义模型外键、relationship与级联models.py里四个模型的完整写法如下from datetime import datetime from app import db class User(db.Model): id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(80), uniqueTrue, nullableFalse) password_hash db.Column(db.String(128), nullableFalse) posts db.relationship(Post, backrefauthor, lazydynamic) class Category(db.Model): id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(50), uniqueTrue, nullableFalse) posts db.relationship(Post, backrefcategory, lazydynamic) class Post(db.Model): id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(120), nullableFalse) body db.Column(db.Text, nullableFalse) created_at db.Column(db.DateTime, defaultdatetime.utcnow) user_id db.Column(db.Integer, db.ForeignKey(user.id), nullableFalse) category_id db.Column(db.Integer, db.ForeignKey(category.id), nullableFalse) comments db.relationship(Comment, backrefpost, lazydynamic, cascadeall, delete-orphan) class Comment(db.Model): id db.Column(db.Integer, primary_keyTrue) body db.Column(db.Text, nullableFalse) created_at db.Column(db.DateTime, defaultdatetime.utcnow) post_id db.Column(db.Integer, db.ForeignKey(post.id), nullableFalse) user_id db.Column(db.Integer, db.ForeignKey(user.id), nullableFalse)几个参数展开说。lazydynamic让posts成为查询对象而不是列表调post.author.posts.count()时不会把全部数据加载进内存如果只是遍历某分类下全部文章改成lazyselect反而更省事因为查询更直接。cascadeall, delete-orphan表示删除文章时自动删除其下所有评论避免孤儿数据这个配置在开发文档的数据完整性章节必须写。db.Column里的nullableFalse配合表单校验可以在数据库层挡住空标题。datetime.utcnow是函数引用而不是调用结果这样每次插入记录时才取当前时间写成datetime.utcnow()就会变成启动时刻的固定值。3.3 用Flask-Migrate迁移表结构和准备种子数据create_all()不能修改已存在的表开发中每改一次字段就要迁移一次标准做法是Flask-Migratepip install flask-migrate flask --app run.py db init flask --app run.py db migrate -m create user category post comment tables flask --app run.py db upgrademigrate命令会扫描models.py与当前数据库的差异生成migrations/versions/下的迁移脚本upgrade才真正执行。开发文档的数据库设计章节里直接贴迁移脚本能证明表结构是可持续演进的而不是删库重建。种子数据单独写成一个命令方便答辩时一键演示import click from flask.cli import with_appcontext from werkzeug.security import generate_password_hash click.command(seed) with_appcontext def seed_command(): from app import db from app.models import User, Category if not User.query.filter_by(usernameadmin).first(): db.session.add(User(usernameadmin, password_hashgenerate_password_hash(admin123))) db.session.add(Category(namePython)) db.session.add(Category(nameFlask)) db.session.commit()click的with_appcontext让这个命令能访问数据库注册进app.cli.add_command(seed_command)后一条flask seed就能把演示数据准备好。种子逻辑判断「没有admin才创建」重复执行不会产生重复数据。4. 用Flask实现博客核心功能登录、文章增删改查与Markdown渲染4.1 用werkzeug做密码哈希与session登录控制先写认证。注册和登录共用同一套密码处理逻辑登录路由如下from flask import Blueprint, render_template, request, session from flask import redirect, url_for, flash from werkzeug.security import check_password_hash from app import db from app.models import User bp Blueprint(auth, __name__) bp.route(/login, methods[GET, POST]) def login(): if request.method POST: username request.form.get(username, ).strip() password request.form.get(password, ) user User.query.filter_by(usernameusername).first() if user and check_password_hash(user.password_hash, password): session[user_id] user.id session[username] user.username return redirect(request.args.get(next) or url_for(blog.index)) flash(用户名或密码错误) return render_template(login.html)check_password_hash用盐值重新计算哈希再比对即使两条记录密码相同密文也不一样。session[user_id]写入后后续请求都能读到当前用户登出就是session.pop(user_id, None)。注意登录校验必须同时满足「用户存在」和「密码匹配」不要把两种情况分开提示否则被用来探测用户名是否存在。受保护路由用一个装饰器统一拦截from functools import wraps def login_required(view): wraps(view) def wrapped(*args, **kwargs): if user_id not in session: flash(请先登录) return redirect(url_for(auth.login, nextrequest.path)) return view(*args, **kwargs) return wrapped装饰器保留原视图的函数名url_for生成URL时才能正确匹配少了wraps会报Endpoint错误。4.2 文章列表分页与增删改查路由的参数设计文章列表页是博客门面分页是必考参数bp.route(/) def index(): page request.args.get(page, 1, typeint) pagination Post.query.order_by(Post.created_at.desc()).paginate( pagepage, per_page5, error_outFalse) return render_template(index.html, postspagination.items, paginationpagination)paginate的参数作用如下参数类型作用建议值pageint当前页码1per_pageint每页条数5或10error_outbool越界时是否抛404Falsepage从哪里来request.args.get(page, 1, typeint)从URL的?page2读取typeint让非数字输入直接回落成默认值1不用手动try except。error_outFalse让越界页码返回空列表而不是404用户体验更好。模板里遍历pagination.items渲染文章卡片底部根据pagination.has_prev和pagination.has_next生成上一页下一页链接。文章的创建、编辑、删除路由结构类似以创建为例bp.route(/post/new, methods[GET, POST]) login_required def new_post(): if request.method POST: title request.form.get(title, ).strip() body request.form.get(body, ).strip() category_id request.form.get(category_id, typeint) if title and body and category_id: post Post(titletitle, bodybody, category_idcategory_id, user_idsession[user_id]) db.session.add(post) db.session.commit() return redirect(url_for(blog.post_detail, post_idpost.id)) flash(标题、正文和分类不能为空) categories Category.query.all() return render_template(new_post.html, categoriescategories)POST成功后必须redirect而不是直接render_template这是PRG模式防止刷新页面重复提交表单。编辑和删除在URL里带post_id参数删除用POST方法而不是GET避免被预加载或爬虫触发。4.3 Markdown渲染与代码高亮fenced_code和codehilite扩展博客正文用Markdown编辑是这类项目的标配渲染用python-markdown库import markdown from markupsafe import Markup blog_bp.app_template_filter(markdown) def render_markdown(text): return Markup(markdown.markdown(text, extensions[fenced_code, codehilite]))注册成模板过滤器后模板里写{{ post.body | markdown }}即可。两个扩展参数很关键fenced_code让围栏代码块生效否则多行代码会粘连成一坨codehilite负责代码高亮配合Pygments生成的CSS使用具体是先运行pygmentize -S default -f html生成样式文件放进static目录。注意markdown.markdown默认不清理HTML标签用户文章里写

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价