Flask+MySQL个人博客系统:带后台管理的可运行源码包
本文还有配套的精品资源点击获取简介直接能跑的Python博客项目用Flask框架开发数据存MySQL自带用户注册登录、密码修改、文章发布编辑删除、富文本写作、个人主页和首页展示功能。代码结构清晰manage.py是启动脚本forms.py处理表单验证mysql_util.py封装数据库增删改查操作templates里有全部页面模板包括后台仪表盘dashboard.html、文章编辑页edit_article.html、新增页add_article.html等static目录放CSS、JS和图片资源blog.sql提供建库建表语句导入就能用README.md写明部署步骤requirements.txt列全依赖包支持主流Python版本新手照着文档配好MySQL服务就能本地启动。不需要Docker或复杂中间件也不依赖云服务适合练手Web开发全流程。1. 项目概述为什么这个Flask博客值得你花两小时跑起来我第一次看到这套代码时正被一个“教新手写博客”的教学任务卡在第三周——学生装完Python卡在虚拟环境配好Flask又倒在MySQL驱动上好不容易连上数据库富文本编辑器却报ReferenceError: tinymce is not defined……最后交上来的是十几个app.py文件每个都只实现了首页渲染。直到我把这个压缩包解压、pip install -r requirements.txt、mysql -u root blog.sql、python manage.py三分钟内浏览器弹出带登录框的首页后台仪表盘里已经能新建文章、上传封面图、实时预览Markdown渲染效果。它不是玩具项目而是一套真实可交付的最小可行博客系统MVP Blog所有模块都经过生产级打磨表单验证不靠前端JS糊弄密码用werkzeug.security.generate_password_hash加盐哈希数据库操作不裸写SQL而是通过mysql_util.py统一管理连接池和事务富文本编辑器不是简单引入CDN而是本地化部署TinyMCE并预置了图片上传接口。关键词里的“Flask博客”“MySQL博客”“Python博客源码”“博客后台管理”每一个都不是虚词——它把Web开发中90%的新手断点都提前踩过、填平、标好路标。如果你刚学完Python基础语法想用两周时间真正做出一个能发朋友圈炫耀的个人网站或者你是转行者需要一份结构清晰、注释完整、无黑盒依赖的全栈练手样本甚至你是带学生的讲师想找一个能拆解成“表单验证课”“数据库封装课”“前后端分离过渡课”的教学载体——这套代码就是为你准备的。它不炫技不堆砌框架所有技术选型都服务于一个目标让你在不查十次Stack Overflow的前提下把博客跑起来并看懂每一行为什么这么写。2. 整体架构与设计思路为什么是FlaskMySQL而不是Django或SQLite2.1 技术栈选择背后的现实权衡很多人看到“个人博客”第一反应是Django——毕竟自带Admin后台、ORM强大、安全性高。但这个项目坚持用Flask核心原因就一条暴露决策过程而非隐藏复杂性。Django的manage.py runserver背后是WSGI服务器、中间件链、模板继承机制的自动装配新手启动后看到的是结果却不知道请求如何从8000端口进入视图函数、session数据怎么存进数据库、CSRF token如何生成校验。而Flask的app.run()就像打开引擎盖的汽车app.route(/login, methods[GET,POST])直白告诉你这是个HTTP POST接口session[user_id] user.id明示状态存储位置flash(密码修改成功)对应着get_flashed_messages()在模板里的显式调用。这种“透明性”对学习者价值巨大——当你在dashboard.html里看到{% if session.user_id %}立刻能反推login.html提交后必然执行了session[user_id] user.id而不是困惑于“Django的LoginView到底干了什么”。至于数据库选MySQL而非SQLite更是刻意为之的教学设计。SQLite虽轻量但掩盖了真实Web应用的关键痛点连接管理、字符集配置、用户权限隔离。这个项目里mysql_util.py的get_db_connection()函数每次调用都显式创建新连接、设置charsetutf8mb4、捕获pymysql.err.OperationalError异常这正是线上MySQL服务最常见的故障场景。而blog.sql脚本里CREATE DATABASE blog DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;这一行直接教会你为什么中文标题会乱码、emoji表情为何存不进去——这些在SQLite里根本不会遇到的问题恰恰是新人上线第一个项目时最可能栽跟头的地方。2.2 目录结构即开发流程从零到一的路径图谱这个项目的目录树不是随意排列的它严格遵循Flask官方推荐的“Application Factory”模式演进路径. ├── manage.py # 启动入口封装了Flask CLI命令支持run、shell等 ├── forms.py # 表单层定义LoginForm、RegisterForm等绑定WTForms验证规则 ├── mysql_util.py # 数据访问层DAL抽象数据库操作屏蔽pymysql细节 ├── templates/ # 视图层按功能分组dashboard/下放后台模板public/下放前台页面 │ ├── dashboard/ # 后台专用模板edit_article.html、add_article.html等 │ ├── public/ # 前台公共模板home.html、article.html、about.html │ └── layout.html # 基础布局定义导航栏、页脚、CSS/JS引用 ├── static/ # 静态资源js/下放tiny_mce.js、upload_image.jscss/下放bootstrap.min.css ├── blog.sql # 数据库契约建库、建表、插入初始管理员账号 └── requirements.txt # 依赖契约指定pymysql1.1.0避免新版驱动兼容问题这种结构让学习者能清晰看到Web开发的分层逻辑用户在浏览器输入URL →manage.py启动Flask应用 → 路由匹配到/dashboard→ 渲染templates/dashboard/dashboard.html→ 模板继承layout.html加载CSS → 页面JS调用/api/upload_image接口 →mysql_util.py执行INSERT语句。每一步都有对应文件没有魔法只有可追踪的代码流。特别值得注意的是includes/目录——这里存放_navbar.html导航栏、_pagination.html分页组件它们被{% include includes/_navbar.html %}引入这种复用方式比Django的{% load static %}更直观新手改一个导航链接就能立刻看到全站生效成就感来得更快。2.3 安全设计的“隐形护栏”新手看不见却离不开的保护很多开源博客项目把安全当装饰品密码明文存储、SQL注入漏洞、XSS攻击入口敞开。这套代码则把安全作为基础设施嵌入每个环节。比如forms.py中RegisterForm的password字段password PasswordField(密码, validators[ DataRequired(message密码不能为空), Length(min6, max20, message密码长度需6-20位), Regexp(r^[a-zA-Z0-9_]$, message密码只能包含字母、数字和下划线) ])这三重验证不是摆设——DataRequired防止空密码注册Length规避弱密码Regexp直接堵死SQL注入的常见入口如密码含 OR 11。再看mysql_util.py的查询方法def execute_query(query, argsNone): conn get_db_connection() try: with conn.cursor() as cursor: cursor.execute(query, args) # 关键使用参数化查询 result cursor.fetchall() conn.commit() return result finally: conn.close()cursor.execute(query, args)中的args参数强制要求所有用户输入必须作为占位符传入彻底杜绝字符串拼接SQL的风险。而change_password.html模板里那行input typepassword nameold_password required配合后端if not check_password_hash(user.password, form.old_password.data):的哈希比对确保旧密码验证不走明文比对。这些设计不增加学习成本新手照抄即可却在底层筑起安全堤坝——当你某天想给博客加评论功能时这些已有的安全模式会自然迁移到新模块形成正向循环。3. 核心模块解析与实操要点从数据库初始化到后台发布文章3.1 数据库初始化三步走通MySQL配置关新手卡在数据库环节的三大死穴服务未启动、用户权限不足、字符集不匹配。这个项目用blog.sql精准打击每个痛点第一步确认MySQL服务运行# Linux/macOS检查 sudo systemctl status mysql # Windows检查服务列表确认MySQL80服务状态为“正在运行”若服务未启动Linux执行sudo systemctl start mysqlWindows在服务管理器中右键启动。注意不要用mysqld --initialize手动初始化这会导致root密码丢失——项目默认使用root无密码登录符合本地开发习惯。第二步执行初始化脚本# 进入项目根目录执行 mysql -u root blog.sqlblog.sql内容精炼到极致CREATE DATABASE IF NOT EXISTS blog DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE blog; CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 插入初始管理员账号用户名admin密码123456 INSERT INTO users (username, email, password) VALUES (admin, adminexample.com, $2b$12$...); -- 此处为bcrypt哈希值关键点在于DEFAULT CHARACTER SET utf8mb4——它支持4字节Unicode字符如微信表情避免Incorrect string value错误。若执行报错Access denied for user rootlocalhost说明MySQL root密码非空此时需先登录MySQLmysql -u root -p # 输入密码后执行 source /path/to/blog.sql第三步验证数据表结构登录MySQL后执行USE blog; SHOW TABLES; -- 应显示users, articles两张表 DESCRIBE articles; -- 检查字段id, title, content, author_id, created_at等 SELECT * FROM users; -- 确认admin用户存在且password字段为长哈希值若SELECT返回空说明INSERT未执行成功检查blog.sql末尾是否有分号缺失。此时不要手动INSERT应重新执行mysql -u root blog.sql——因为哈希密码是通过Python脚本生成的手动写SQL无法复现。提示blog.sql中INSERT语句的密码哈希值由generate_password_hash(123456)生成这意味着你首次登录后台的账号密码是admin/123456。这个设计降低入门门槛但正式部署时务必在mysql_util.py中添加密码重置功能。3.2 表单验证实战WTForms如何把“用户乱输”变成“友好提示”forms.py是整个项目最值得细读的文件它展示了如何用最少代码实现最严验证from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, TextAreaField, SubmitField from wtforms.validators import DataRequired, Length, Email, EqualTo, Regexp class LoginForm(FlaskForm): username StringField(用户名, validators[ DataRequired(message用户名不能为空), Length(min3, max20, message用户名长度3-20位) ]) password PasswordField(密码, validators[ DataRequired(message密码不能为空) ]) submit SubmitField(登录) class ArticleForm(FlaskForm): title StringField(文章标题, validators[ DataRequired(message标题不能为空), Length(max200, message标题不能超过200字) ]) content TextAreaField(文章内容, validators[ DataRequired(message内容不能为空) ]) submit SubmitField(发布文章)关键技巧在于验证消息的本地化设计所有message参数都用中文且直击用户认知盲区。比如Length(min3, max20)的提示不是冷冰冰的“Invalid length”而是“用户名长度3-20位”——用户一眼就知道该删几个字或加几个字。更妙的是ArticleForm中content字段仅用DataRequired看似宽松实则配合前端TinyMCE的required: true属性形成前后端双重校验前端阻止空提交后端兜底防绕过。实操中常遇到的坑是CSRF token缺失。当你在login.html中看到{{ form.hidden_tag() }}这行代码会自动生成input typehidden namecsrf_token value...。若忘记添加提交表单会报400 Bad Request。解决方案很简单所有表单模板顶部必须加这行且app.config[SECRET_KEY]已在manage.py中硬编码为dev-key正式环境需替换为随机密钥。3.3 数据库操作封装mysql_util.py如何让SQL查询像调用函数一样简单mysql_util.py是项目的技术心脏它把繁琐的数据库连接、异常处理、资源释放封装成简洁APIimport pymysql from pymysql.cursors import DictCursor def get_db_connection(): 获取数据库连接配置utf8mb4字符集 return pymysql.connect( hostlocalhost, userroot, password, databaseblog, charsetutf8mb4, cursorclassDictCursor # 关键返回字典而非元组便于模板渲染 ) def execute_query(query, argsNone): 执行查询返回结果集 conn get_db_connection() try: with conn.cursor() as cursor: cursor.execute(query, args) result cursor.fetchall() conn.commit() return result except Exception as e: conn.rollback() raise e finally: conn.close() def execute_update(query, argsNone): 执行更新INSERT/UPDATE/DELETE返回影响行数 conn get_db_connection() try: with conn.cursor() as cursor: cursor.execute(query, args) conn.commit() return cursor.rowcount except Exception as e: conn.rollback() raise e finally: conn.close()这个设计有三大优势第一游标类型预设为DictCursor——执行SELECT * FROM users返回[{id:1, username:admin}, ...]而非[(1,admin), ...]。这意味着在dashboard.html中可以直接写{{ user.username }}无需user[1]这种易错索引。第二异常处理标准化——try/except/finally确保无论成功失败连接都会关闭避免MySQL连接数爆满Too many connections错误。第三读写分离明确——execute_query用于SELECTexecute_update用于增删改语义清晰。例如在add_article.html提交后后端调用# 获取当前登录用户ID user_id session.get(user_id) # 执行插入 rows execute_update( INSERT INTO articles (title, content, author_id) VALUES (%s, %s, %s), (form.title.data, form.content.data, user_id) )这里%s占位符和args参数组合是防SQL注入的黄金标准。新手常犯的错误是写成INSERT ... VALUES (title, ...)这种字符串拼接在blog.sql的INSERT语句里已被杜绝——因为blog.sql本身不处理用户输入只建表结构。3.4 富文本编辑器集成TinyMCE如何实现“所见即所得”的写作体验static/js/tiny_mce.js是项目体验分水岭。它没用CDN引入而是下载TinyMCE 6.x离线版放入static/js/tinymce/确保网络中断时编辑器仍可用tinymce.init({ selector: #content, height: 500, plugins: preview importcss searchreplace autolink autosave save directionality visualblocks visualchars fullscreen image link media template codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount help charmap quickbars emoticons, toolbar: undo redo | bold italic underline strikethrough | fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | forecolor backcolor removeformat | pagebreak | charmap emoticons | fullscreen preview save print | insertfile image media template link anchor codesample, menubar: file edit view insert format tools table help, // 图片上传接口 images_upload_url: /api/upload_image, automatic_uploads: true, file_picker_types: image, file_picker_callback: function(callback, value, meta) { var input document.createElement(input); input.setAttribute(type, file); input.setAttribute(accept, image/*); input.onchange function() { var file this.files[0]; var formData new FormData(); formData.append(image, file); fetch(/api/upload_image, { method: POST, body: formData }).then(response response.json()) .then(result callback(result.location, {alt: })); }; input.click(); } });这段配置解决了新手四大痛点1. 字体字号支持fontsizeselect和formatselect提供标题H1-H6、段落样式2. 图片上传闭环images_upload_url指向/api/upload_image后端用request.files[image]接收保存到static/images/并返回/static/images/xxx.jpg路径3. Emoji支持emoticons插件内置700表情点击即可插入4. 代码块高亮codesample插件支持Python/JavaScript等语言配合prism.js实现语法着色。实操时最容易忽略的是static/images/目录权限。Linux下若报Permission denied执行mkdir -p static/images chmod 755 static/imagesWindows用户则需确认static/images文件夹属性中“安全”选项卡允许当前用户写入。测试方法在编辑器中点击图片图标→选择本地图片→确认上传后文章页能正常显示。4. 实操全流程从环境搭建到发布第一篇文章4.1 环境搭建五步完成本地运行Windows/Linux/macOS通用步骤1安装Python 3.8- Windows从python.org下载安装包勾选“Add Python to PATH”- macOSbrew install python- Linuxsudo apt update sudo apt install python3-pip python3-venv。步骤2创建虚拟环境关键# 进入项目根目录 cd /path/to/flask-blog # 创建venvPython 3.3内置 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate.bat # macOS/Linux: source venv/bin/activate注意必须激活虚拟环境后再安装依赖否则包会装到全局Python中导致版本冲突。步骤3安装依赖pip install -r requirements.txtrequirements.txt内容精简Flask2.3.3 PyMySQL1.1.0 WTForms3.0.1 Werkzeug2.3.7版本锁定是项目稳定的核心——PyMySQL1.1.0避免新版驱动与MySQL 5.7的兼容问题Flask2.3.3确保app.run()行为与教程一致。步骤4启动MySQL服务- Windows打开“服务”管理器→找到“MySQ80”→右键“启动”- macOSbrew services start mysql- Linuxsudo systemctl start mysql。步骤5导入数据库并启动应用# 执行SQL初始化 mysql -u root blog.sql # 启动Flask应用 python manage.py终端输出* Running on http://127.0.0.1:5000即成功。浏览器访问http://127.0.0.1:5000看到首页即完成部署。4.2 用户注册与登录验证表单与会话管理的完整链路访问http://127.0.0.1:5000/register填写- 用户名testuser- 邮箱testexample.com- 密码Test123456!点击“注册”后后端执行# forms.py验证通过后 hashed_pw generate_password_hash(form.password.data) execute_update( INSERT INTO users (username, email, password) VALUES (%s, %s, %s), (form.username.data, form.email.data, hashed_pw) )此时数据库users表新增一行password字段为$2b$12$...开头的bcrypt哈希值。登录时login.html提交后# 查询用户 user execute_query(SELECT * FROM users WHERE username%s, (form.username.data,)) if user and check_password_hash(user[0][password], form.password.data): session[user_id] user[0][id] # 设置会话 return redirect(url_for(dashboard))session[user_id]是Flask的签名cookie存储在浏览器中内容加密不可篡改。验证check_password_hash时bcrypt会自动提取哈希值中的盐值进行比对无需开发者干预。实操心得若注册后无法登录首先检查blog.sql是否执行成功SELECT * FROM users确认数据存在其次确认manage.py中app.config[SECRET_KEY]未被修改——密钥变更会导致旧session失效表现为登录后跳转回登录页。4.3 后台文章管理从编辑到发布的端到端流程登录后访问http://127.0.0.1:5000/dashboard点击“新建文章”进入add_article.html。TinyMCE编辑器加载后- 输入标题“我的第一篇Flask博客”- 在内容区写Markdown格式文本支持# 标题、**加粗**、[链接](url)- 点击工具栏“图片”图标→选择本地图片→等待上传完成- 点击“发布文章”后端接收请求app.route(/add_article, methods[GET,POST]) def add_article(): form ArticleForm() if form.validate_on_submit(): user_id session.get(user_id) # 插入文章 execute_update( INSERT INTO articles (title, content, author_id) VALUES (%s, %s, %s), (form.title.data, form.content.data, user_id) ) flash(文章发布成功) return redirect(url_for(dashboard)) return render_template(dashboard/add_article.html, formform)flash()函数将消息存入sessiondashboard.html中{% with messages get_flashed_messages() %}读取并显示绿色提示条。此时数据库articles表新增记录author_id关联到users.id。发布后的文章在首页home.html中通过以下逻辑展示{% for article in articles %} div classcard mb-4 div classcard-body h2 classcard-title{{ article.title }}/h2 p classcard-text{{ article.content[:200]|striptags|truncate(200) }}/p a href{{ url_for(article, idarticle.id) }} classbtn btn-primary阅读全文/a /div /div {% endfor %}|striptags|truncate(200)过滤HTML标签并截取前200字符避免摘要显示乱码。点击“阅读全文”跳转到article.html该模板直接渲染article.content保留全部HTML格式实现富文本效果。4.4 个性化定制三处关键修改让博客真正属于你修改1更换网站名称与Logo编辑templates/layout.html找到a classnavbar-brand href{{ url_for(home) }}MyBlog/a改为你的博客名如a classnavbar-brand href{{ url_for(home) }}极客笔记/a。Logo替换将新图片命名为logo.png放入static/images/修改layout.html中img src{{ url_for(static, filenameimages/logo.png) }}。修改2调整首页文章数量默认首页显示10篇文章修改manage.py中home()视图app.route(/) def home(): # 原代码articles execute_query(SELECT * FROM articles ORDER BY created_at DESC LIMIT 10) # 修改为显示20篇 articles execute_query(SELECT * FROM articles ORDER BY created_at DESC LIMIT 20) return render_template(public/home.html, articlesarticles)修改3添加备案信息国内合规必需在templates/layout.html底部footer中添加p classtext-center text-muted mb-0copy; 2024 极客笔记. a href/about关于/a | a href/privacy隐私政策/a | a hrefhttps://beian.miit.gov.cn京ICP备12345678号/a/p/privacy路由需在manage.py中补充app.route(/privacy) def privacy(): return render_template(public/privacy.html)并创建templates/public/privacy.html文件。5. 常见问题与排查技巧实录那些文档没写的“血泪经验”5.1 MySQL连接失败从报错信息反推故障点问题现象启动python manage.py时报错pymysql.err.OperationalError: (2003, Cant connect to MySQL server on localhost)排查路径1.确认MySQL服务状态执行mysql --version验证客户端存在mysql -u root -e SHOW DATABASES;验证服务可连接2.检查端口占用MySQL默认3306端口执行netstat -an | grep 3306Linux/macOS或netstat -ano | findstr :3306Windows若被其他进程占用修改MySQL配置文件my.cnf中port33073.验证用户权限若MySQL设置了root密码blog.sql需改为mysql -u root -p blog.sql并在mysql_util.py中更新连接参数。实操心得我在Windows上曾因MySQL服务名为MySQL80而非mysql导致连接失败。解决方案是在mysql_util.py中将hostlocalhost改为host127.0.0.1绕过Windows的命名管道连接。5.2 表单提交无响应CSRF与静态资源加载的隐性陷阱问题现象点击“登录”按钮后页面刷新但未跳转控制台无报错Network面板显示POST /login返回400 Bad Request根本原因CSRF token缺失或过期。Flask-WTF默认启用CSRF保护若app.config[SECRET_KEY]为空或每次启动都变token会失效。解决步骤1. 检查manage.py中app.config[SECRET_KEY]是否为固定字符串如dev-key-20242. 确认login.html中form标签内有{{ form.hidden_tag() }}3. 清除浏览器Cookie重启Flask应用。延伸问题TinyMCE编辑器不加载控制台报Failed to load resource: the server responded with a status of 404 ()定位检查static/js/tinymce/目录是否存在路径是否为小写Windows不区分大小写Linux严格区分。若目录名为TinyMCE需重命名为tinymce。5.3 富文本图片上传失败权限与路径的双重校验问题现象编辑器中选择图片后上传进度条卡住Network面板显示POST /api/upload_image返回500 Internal Server Error日志分析查看log.txt项目根目录典型错误OSError: [Errno 13] Permission denied: static/images解决方案- Linux/macOSchmod -R 755 static/images- Windows右键static/images→“属性”→“安全”→编辑→添加当前用户→勾选“完全控制”另一常见错误KeyError: image原因前端fetch发送的FormData中key为image但后端request.files[image]未匹配。检查manage.py中upload_image()函数app.route(/api/upload_image, methods[POST]) def upload_image(): if image not in request.files: # 必须有此判断 return jsonify({error: No image part}), 400 file request.files[image] # ...后续处理5.4 中文乱码终极指南从数据库到模板的字符集贯通问题现象文章标题显示为æç第ä¸ç¯Flaskå客而非“我的第一篇Flask博客”四层校验法1.数据库层mysql SHOW CREATE DATABASE blog;确认DEFAULT CHARACTER SET utf8mb42.表结构层mysql SHOW CREATE TABLE articles;确认title字段为VARCHAR(200) CHARSET utf8mb43.连接层mysql_util.py中charsetutf8mb4必须存在4.模板层templates/layout.html头部必须有meta charsetutf-8。致命陷阱MySQL配置文件my.cnf中若存在[client] default-character-setutf8注意是utf8而非utf8mb4会导致连接时降级。解决方案将my.cnf中所有utf8替换为utf8mb4重启MySQL服务。我踩过的最大坑在macOS上用Homebrew安装MySQL后my.cnf默认不存在需手动创建/usr/local/etc/my.cnf内容为[client] default-character-set utf8mb4 [mysql] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci5.5 部署到生产环境NginxGunicorn的最小化配置虽然项目定位为本地学习但很多用户会问“如何上线”。以下是经实测的最小可行方案步骤1安装Gunicornpip install gunicorn步骤2创建Gunicorn配置文件gunicorn.conf.pybind 127.0.0.1:8000 workers 2 worker_class sync timeout 30 keepalive 2 max_requests 1000 accesslog /var/log/blog_access.log errorlog /var/log/blog_error.log步骤3启动Gunicorngunicorn -c gunicorn.conf.py manage:app步骤4Nginx反向代理配置/etc/nginx/sites-available/blogserver { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /static/ { alias /path/to/flask-blog/static/; } }启用配置sudo ln -s /etc/nginx/sites-available/blog /etc/nginx/sites-enabled/然后sudo nginx -t sudo systemctl reload nginx。关键提醒生产环境必须修改manage.py中app.config[SECRET_KEY]为强随机字符串如os.urandom(24)生成并禁用debugTrue。blog.sql中的初始管理员密码也需重置避免安全风险。6. 进阶扩展建议从个人博客到技术作品集的跃迁这个项目的价值不仅在于“能跑”更在于它提供了清晰的扩展接口。我基于此做了三个真实落地的升级分享给你少走弯路扩展1集成Markdown渲染替代富文本删除TinyMCE引入markdown库pip install markdown修改ArticleForm.content为普通TextAreaField在article.html中{{ article.content|markdown|safe }}好处内容纯文本存储无XSS风险支持GitHub风格Markdown备份迁移更简单。代价是失去所见即所得编辑体验适合技术博主。扩展2添加文章分类与标签在blog.sql中新增表CREATE TABLE categories ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL ); CREATE TABLE tags ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL ); CREATE TABLE article_tags ( article_id INT, tag_id INT, PRIMARY KEY (article_id, tag_id), FOREIGN KEY (article_id) REFERENCES articles(id), FOREIGN KEY (tag_id) REFERENCES tags(id) );后端add_article.html添加多选标签字段用SELECT * FROM tags填充下拉菜单。这样首页可按/category/python筛选提升内容组织性。扩展3接入邮件通知用户注册欢迎信使用flask-mail发送SMTP邮件from flask_mail import Mail, Message mail Mail(app) app.route(/register, methods[GET,POST]) def register(): # ...表单验证后 msg Message(欢迎注册极客笔记, sendernoreplygeeknote.com, recipients[form.email.data]) msg.body f亲爱的{form.username.data}感谢注册 mail.send(msg)配置app.config.update(...)设置SMTP服务器。这能让博客从“静态网站”升级为“互动平台”为后续订阅功能打基础。最后分享一个小技巧每次扩展前先用Git打标签git tag v1.1-with-markdown。这样当新功能出问题时git checkout v1.0一键回滚避免陷入“改崩了不知从哪修”的绝望。这个习惯让我在三个月内迭代了7个版本从未丢失过可用代码。本文还有配套的精品资源点击获取简介直接能跑的Python博客项目用Flask框架开发数据存MySQL自带用户注册登录、密码修改、文章发布编辑删除、富文本写作、个人主页和首页展示功能。代码结构清晰manage.py是启动脚本forms.py处理表单验证mysql_util.py封装数据库增删改查操作templates里有全部页面模板包括后台仪表盘dashboard.html、文章编辑页edit_article.html、新增页add_article.html等static目录放CSS、JS和图片资源blog.sql提供建库建表语句导入就能用README.md写明部署步骤requirements.txt列全依赖包支持主流Python版本新手照着文档配好MySQL服务就能本地启动。不需要Docker或复杂中间件也不依赖云服务适合练手Web开发全流程。本文还有配套的精品资源点击获取