ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Flask+MySQL博客系统开发实战:从数据库设计到部署

2026/9/16 14:37:37 拓冰建站 浏览量
Flask+MySQL博客系统开发实战:从数据库设计到部署 简介这是一份面向高校计算机专业学生的Python毕业设计项目基于Flask轻量级框架与MySQL数据库构建完整博客系统适用于课程设计、毕业设计或Flask入门进阶练习。压缩包共571个文件包含43个Python后端源码、111个Vue前端组件、159个SVG图标、44个PNG图片、63个JS脚本、15个CSS样式以及SQL数据库文件和docx开发文档整体约21.48MB目录清晰便于定位。项目覆盖用户管理、标签管理、分类管理、文章管理、公告资讯管理、举报信息管理、消息通知管理和系统管理等多个模块并附带数据库初始化脚本与多个bat快捷启动文件方便本地快速运行。目前已有135人学习配合源码、数据库、运行教程和逻辑讲解可帮助理解Flask项目结构、博客业务设计及前后端协作方式是一份可直接参考的完整毕设方案。1. 拿到一份Flask博客毕设代码先别急着双击run.bat很多同学从导师那里拿到「基于Flask的博客系统」源码包第一反应是解压、双击run.bat、看到页面就以为自己会了。但答辩时被问“为什么用户表不用视图”“文章和标签为什么不用外键级联”就卡住。这个项目包含源码、MySQL数据库文件和开发文档功能覆盖用户管理、文章管理、标签分类、公告、举报、消息通知等正好是Flask MySQL的完整闭环。不管是做毕设还是想补Flask Web开发的基础都可以把它当成一个拆解样本。下面从表结构开始按我的思路把这个博客系统的关键实现讲清楚。2. Flask MySQL博客系统的数据模型与建表细节2.1 用户表设计不只有用户名和密码项目里“用户管理”和“我的信息”都落在用户表上。常见做法是设计一张user表但不要把密码明文存。Flask生态里常用werkzeug.security生成密码哈希所以字段类型选VARCHAR(128)而不是写死长度。下面这张表是该项目里比较典型的用户模型CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL COMMENT 登录名, password_hash VARCHAR(128) NOT NULL COMMENT 密码哈希, nickname VARCHAR(50) DEFAULT NULL COMMENT 昵称, email VARCHAR(100) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像地址, role TINYINT DEFAULT 0 COMMENT 0普通用户 1管理员, status TINYINT DEFAULT 1 COMMENT 0禁用 1正常, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;角色用TINYINT就够了不需要单独建角色表。UNIQUE KEY保证用户名不能重复这是用户管理里“新增用户”时唯一性校验的基础。密码哈希字段用128是因为generate_password_hash默认输出60多位留足余量。表设计要注意状态字段status默认1后台禁用用户时只需改这个字段不需要删数据。这在举报信息管理里尤其重要——被举报用户应该先禁用而不是直接删除否则文章外键会变得很难看。另外所有表都用InnoDB和utf8mb4支持事务也支持emoji头像。这里插入一个表清单方便对照项目中各个功能模块对应的数据表。表名作用关键字段user用户信息username, password_hash, rolecategory文章分类name, sort_ordertag标签namearticle文章title, content, user_id, statusarticle_tag文章标签关联article_id, tag_idnotice公告资讯title, content, statusreport举报信息reporter_id, target_type, target_id, reasonmessage消息通知user_id, content, is_read实际项目里还会加create_time、update_time这里为了简洁省略。系统管理模块在源码里通常是一组站点配置的增删改查表结构可以是 key-value 的形式与消息通知表类似登录后写入配置项即可。2.2 文章、分类、标签多对多关系怎么建文章和分类、标签是博客系统的核心。分类通常是一对多一篇文章只属于一个分类标签是多对多一篇文章可以有多个标签。这个项目里文章表是article分类表是category标签表是tag另外用一张关联表处理多对多关系。CREATE TABLE article ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, category_id INT DEFAULT NULL, title VARCHAR(200) NOT NULL, content LONGTEXT, status TINYINT DEFAULT 1 COMMENT 0草稿 1发布, view_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;分类和标签表结构更简单就id、name和排序字段。关联表用复合主键避免重复CREATE TABLE article_tag ( article_id INT NOT NULL, tag_id INT NOT NULL, PRIMARY KEY (article_id, tag_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;为什么不用JSON字段存标签因为标签管理模块里要统计每个标签下有多少文章JSON 字段做不了高效的GROUP BY。用关联表后统计标签文章数就是一次JOIN加COUNT。另外注意article.category_id上建了索引因为列表页经常按分类过滤。我自己写博客系统时连status也会加索引但项目的初始化SQL里没加数据量大了再补也不迟。2.3 公告、举报、消息通知怎么落在表里公告资讯管理在后台是一个独立的notice表发布状态用status控制。举报信息管理需要记录“谁举报了谁、为什么举报”所以设计成下面这样CREATE TABLE report ( id INT NOT NULL AUTO_INCREMENT, reporter_id INT NOT NULL COMMENT 举报人, target_type TINYINT DEFAULT 1 COMMENT 1文章 2评论 3用户, target_id INT NOT NULL, reason VARCHAR(255) DEFAULT NULL, handle_status TINYINT DEFAULT 0 COMMENT 0待处理 1已处理, handle_result VARCHAR(255) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;消息通知管理则是一张message表收件人是user_idis_read标记已读未读。管理员回复举报时业务逻辑里会往message表插入一条通知然后前台用户登录后能看到。这里的处理方式是在后台管理里直接生成消息而不是走异步队列对毕设项目来说已经够用。三张表都不需要复杂外键约束因为Flask的SQLAlchemy在应用层维护关系。实际部署时如果使用MySQL 5.7以上版本注意把sql_mode里的ONLY_FULL_GROUP_BY处理掉否则统计类SQL容易报错。3. 用蓝图和SQLAlchemy把后端业务撑起来3.1 应用工厂和蓝图注册把admin和blog拆开Flask项目最怕把所有路由写在一个app.py里这个毕设源码是拆了蓝图的。常见的目录结构是apps/admin和apps/blog两个蓝图一个管后台一个管前台。在app.py里用应用工厂初始化扩展from flask import Flask from flask_sqlalchemy import SQLAlchemy db SQLAlchemy() def create_app(): app Flask(__name__) app.config.from_pyfile(config.py) db.init_app(app) from apps.admin import admin_bp from apps.blog import blog_bp app.register_blueprint(admin_bp, url_prefix/admin) app.register_blueprint(blog_bp) return app配置文件里要写清楚数据库连接串SQLALCHEMY_DATABASE_URI mysqlpymysql://root:123456localhost:3306/blog_db?charsetutf8mb4 SQLALCHEMY_TRACK_MODIFICATIONS False SECRET_KEY your-secret-keyurl_prefix/admin是后台蓝图统一前缀这样权限检查可以在这层做。注册蓝图后admin_bp里的所有路由都会自动挂在/admin下比如admin_bp.route(/user/list)最终就是/admin/user/list。开发文档里通常会写清楚每个蓝图对应哪些模块照着拆就成。3.2 用户登录与session管理用户模块是博客系统的第一道门。项目里使用了Flask自带的session加werkzeug.security做密码校验。登录视图大致是这样的from flask import Blueprint, request, redirect, url_for, session from werkzeug.security import check_password_hash from models import User blog_bp Blueprint(blog, __name__) blog_bp.route(/login, methods[POST]) def login(): username request.form.get(username) password request.form.get(password) user User.query.filter_by(usernameusername).first() if user and user.status 1 and check_password_hash(user.password_hash, password): session[user_id] user.id session[role] user.role return redirect(url_for(blog.index)) return 用户名或密码错误, 401这里的关键点是先通过用户名查用户再调用check_password_hash验证密码。有的初学者喜欢取出所有用户遍历比较那是错的。session[role]存的是用户角色后台管理员中间件检查是否等于1。退出登录时记得session.clear()。要注意线上环境SECRET_KEY必须改不能是源码里的默认值否则攻击者可以伪造session。毕设演示可以忽略但答辩时主动提出来是加分项。3.3 文章管理的CRUD和分页文章管理是重头戏。后台展示文章列表要求分页Flask-SQLAlchemy 自带paginate方法admin_bp.route(/article/list) def article_list(): page request.args.get(page, 1, typeint) per_page 10 pagination Article.query.order_by(Article.create_time.desc()).paginate( pagepage, per_pageper_page, error_outFalse ) articles pagination.items return render_template(admin/article_list.html, paginationpagination, articlesarticles)error_outFalse的作用是当页码超出范围时返回空列表而不是抛404这样前端翻页组件不容易崩。模板里用pagination.iter_pages()生成页码链接。新增或编辑文章时要注意category_id外键的处理。Flask-SQLAlchemy 的表关系定义好了之后可以用Article.category直接访问分类对象。提交表单后先查分类是否存在避免写入无效外键admin_bp.route(/article/add, methods[POST]) def article_add(): title request.form.get(title) category_id request.form.get(category_id, typeint) content request.form.get(content) category Category.query.get(category_id) if not category: return 分类不存在, 400 article Article(titletitle, contentcontent, category_idcategory_id) db.session.add(article) db.session.commit() return redirect(url_for(admin.article_list))批量操作如删除选中文章常见做法是用request.form.getlist(ids)拿到列表循环删除。这里不要用DELETE FROM article WHERE id IN (...)直接拼SQL很容易被注入。SQLAlchemy 的Article.query.filter(Article.id.in_(ids)).delete(synchronize_sessionFalse)更安全。4. 从Windows双击脚本到Linux服务器上线4.1 项目里的.bat脚本到底做了什么项目包里有一堆.bat脚本1-install.bat、init_sql.bat、run.bat等。这些主要是为了在Windows上减少命令行操作我一般会先挨个看一眼内容再双击。典型内容如下pip install -r requirements.txt这是1-install.bat的核心安装依赖。init_sql.bat一般是mysql -uroot -p123456 -e source database/blog_db.sqlrun.bat里则是python app.py注意如果项目里既有run.bat又有2-run.bat可能是打包时重复文件。我的建议是不要直接双击而是打开 CMD 手动执行因为双击运行报错了窗口马上关闭看不到堆栈信息。脚本实际做的事情建议替代命令1-install.batpip安装依赖pip install -r requirements.txtinit_sql.bat导入MySQL数据库mysql -uroot -p database.sqlrun.bat启动Flask应用python app.pybuild.bat可能用于打包或编译视内容而定4.2 换到Linuxgunicorn nginx部署毕设答辩时如果能把项目部署到Linux服务器说服力会强很多。Flask内置服务器只能用于开发生产环境一般用 gunicorn。pip install gunicorn gunicorn -w 4 -b 127.0.0.1:8000 app:app-w 4表示4个worker进程-b指定监听地址。此时直接用 IP 加端口访问会看到界面但静态文件加载不对需要nginx处理静态资源和反向代理server { listen 80; server_name your-domain.com; location /static { alias /path/to/project/static; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }init_sql.bat在Linux下没有用mysql -uroot -p database.sql导入数据库即可。另外记得在MySQL里创建对应的数据库CREATE DATABASE IF NOT EXISTS blog_db DEFAULT CHARSET utf8mb4;4.3 首次运行最常见的三个坑第一个坑是数据库连接失败。Flask应用启动时并不会立刻访问数据库但一请求就报OperationalError。优先检查config.py里的用户名、密码、host、端口是否和MySQL一致。注意localhost和127.0.0.1在MySQL权限表里是两个不同的host。第二个坑是端口被占用。Windows下双击run.bat后提示Port 5000 is in use常见原因是其它Python进程占用了5000端口。CMD里执行netstat -ano | findstr :5000找到PID然后taskkill /PID 进程号 /F。第三个坑是中文乱码。页面显示???或者保存文章后读取乱码检查MySQL数据库和表的字符集建库时指定utf8mb4连接串里加?charsetutf8mb4。另外app.config[JSON_AS_ASCII] False可以让登录接口的提示返回中文而不是\u转义。5. 让演示更顺滑初始化数据与我的信息页面的小改动5.1 用Python脚本灌入默认管理员和示例文章项目里的init_sql.bat只导入了表结构演示时最好有可用数据。我一般会写一个init_data.py放在项目根目录运行一次就能看到效果from werkzeug.security import generate_password_hash from app import create_app, db from models import User, Category, Article app create_app() with app.app_context(): if not User.query.filter_by(usernameadmin).first(): admin User(usernameadmin, password_hashgenerate_password_hash(admin123), role1) db.session.add(admin) c1 Category(nameFlask) db.session.add(c1) db.session.commit() a1 Article(title第一篇博客, content这是一个用Flask写的博客系统…, category_idc1.id, user_idadmin.id) db.session.add(a1) db.session.commit()这个脚本的好处是幂等每次运行前检查用户是否存在不会重复插入。答辩演示时用admin登录后台就能看到文章管理列表里已经有一条数据不用现场敲。5.2 自定义过滤器和“我的信息”联调前台“我的信息”页面除了展示用户资料还要显示我的文章数、被举报次数等统计。这里可以注册一个自定义过滤器把datetime格式化成更友好的文本app.template_filter(format_time) def format_time(value): return value.strftime(%Y-%m-%d %H:%M) if value else 模板里直接{{ article.create_time | format_time }}。在“我的信息”页用当前登录用户的session[user_id]去查文章数Article.query.filter_by(user_idsession[user_id]).count()。再把头像上传表单的enctype改成multipart/form-data否则文件上传取不到内容。最后一步是打开浏览器进入个人中心确认头像、昵称、文章数能正确显示这样演示的时候不会出现信息对不上的尴尬。本文还有配套的精品资源点击获取