
简介这是一套基于Python Flask框架实现的完整个人博客系统源码面向Web开发初学者与Python后端入门者旨在帮助学习者掌握轻量级Web应用的核心开发流程。资源包含1130个文件涵盖42个Python后端逻辑文件含路由、模型、视图及配置、81个HTML模板页支持Jinja2动态渲染、738个JS前端交互脚本含Bootstrap主题适配与响应式功能以及40个CSS/SCSS样式文件预置lumen、flatly、solar等10种Bootstrap主题整体压缩包大小为27.3MB。目前已有803人下载学习。读者可直接运行项目体验用户注册登录、文章CRUD、权限分级管理普通用户/管理员、数据库ORM操作Flask-SQLAlchemy、静态资源组织及自定义错误页等完整功能模块代码结构清晰、注释充分是理解Flask核心机制与全栈开发实践的理想参考范例。1. 从零到一为什么选择Flask来搭建你的个人博客如果你正在寻找一个能让你从零开始亲手搭建一个功能完整、风格独特的个人博客的框架那么Flask几乎是一个无需犹豫的选择。我见过太多开发者无论是刚入门Python的新手还是想找个轻量级框架快速验证想法的老手最终都绕不开Flask。它不像Django那样自带“全家桶”给你一套既定的规则和目录结构而是更像一个工具箱给你最核心的“螺丝刀”和“扳手”至于怎么设计、怎么组装完全由你说了算。这种“微”框架的哲学恰恰是学习Web开发、理解HTTP请求响应、数据库操作、模板渲染等核心概念的绝佳路径。通过搭建一个博客系统你几乎能接触到Web后端开发的所有基础环节用户认证、文章增删改查、静态文件管理、表单处理、部署上线。整个过程下来你对Web应用的理解会远比单纯使用一个现成CMS要深刻得多。很多人问现在有WordPress、Hexo、Hugo这么多成熟的方案为什么还要自己写我的体会是自己动手的意义不在于“造轮子”而在于“理解轮子”。当你亲手处理了从浏览器输入网址到服务器返回一个渲染好的HTML页面的每一个环节时那种对技术栈的掌控感和解决问题的成就感是使用现成工具无法比拟的。更何况一个完全由你定制的博客从数据库设计到前端样式没有任何冗余功能运行起来轻快无比这种体验非常美妙。2. 蓝图与骨架如何规划一个可维护的Flask博客项目结构直接上手就写app.py把所有路由、视图函数、配置都塞进去是新手最常见的做法但也是项目走向混乱的开端。一个中等复杂度的博客系统至少会涉及用户管理、文章管理、评论管理、后台管理等多个模块。如果所有代码都堆在一起不出一个月连你自己都不想再碰这个项目了。2.1 采用工厂模式与蓝图Blueprint进行模块化Flask的工厂模式Application Factory和蓝图是解决这个问题的标准答案。工厂模式让我们可以动态创建应用实例这在测试和需要创建多个应用实例时非常有用。而蓝图则像乐高积木允许我们把不同功能模块的路由、模板、静态文件分开管理。一个我实践下来非常清晰的项目结构是这样的my_blog/ ├── app/ │ ├── __init__.py # 应用工厂函数所在地 │ ├── models.py # 数据库模型定义如User, Post, Comment │ ├── extensions.py # 扩展初始化如SQLAlchemy, LoginManager │ ├── config.py # 配置类DevelopmentConfig, ProductionConfig │ ├── auth/ # 认证蓝图 │ │ ├── __init__.py │ │ ├── forms.py # 登录、注册表单 │ │ ├── routes.py # 登录、注册、登出路由 │ │ └── templates/ # 认证相关模板 │ ├── blog/ # 博客主蓝图 │ │ ├── __init__.py │ │ ├── forms.py # 文章发布、评论表单 │ │ ├── routes.py # 首页、文章详情、分类路由 │ │ └── templates/ │ ├── admin/ # 后台管理蓝图 │ │ ├── __init__.py │ │ └── routes.py │ ├── static/ # 全局静态文件CSS, JS, images │ └── templates/ # 全局基础模板 │ ├── base.html │ └── ... ├── migrations/ # 数据库迁移文件夹Flask-Migrate生成 ├── tests/ # 单元测试 ├── venv/ # Python虚拟环境不要提交到Git ├── .env # 环境变量SECRET_KEY等不要提交 ├── .gitignore ├── config.py # 根目录配置用于加载环境变量 ├── requirements.txt # 项目依赖 └── run.py # 应用启动入口在app/__init__.py中你的应用工厂函数核心逻辑如下from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager from flask_migrate import Migrate from app.config import Config # 先初始化扩展但不绑定app db SQLAlchemy() login_manager LoginManager() migrate Migrate() def create_app(config_classConfig): app Flask(__name__) app.config.from_object(config_class) # 初始化扩展绑定app db.init_app(app) login_manager.init_app(app) migrate.init_app(app, db) # 注册蓝图 from app.auth import bp as auth_bp app.register_blueprint(auth_bp, url_prefix/auth) from app.blog import bp as blog_bp app.register_blueprint(blog_bp) from app.admin import bp as admin_bp app.register_blueprint(admin_bp, url_prefix/admin) return app2.2 配置管理的艺术区分开发与生产环境直接把SECRET_KEY、数据库密码写在代码里是绝对的大忌。我推荐使用python-dotenv加载环境变量并创建不同的配置类。根目录下的config.pyimport os from dotenv import load_dotenv basedir os.path.abspath(os.path.dirname(__file__)) load_dotenv(os.path.join(basedir, .env)) class Config: SECRET_KEY os.environ.get(SECRET_KEY) or you-will-never-guess SQLALCHEMY_DATABASE_URI os.environ.get(DATABASE_URL) or \ sqlite:/// os.path.join(basedir, app.db) SQLALCHEMY_TRACK_MODIFICATIONS False class DevelopmentConfig(Config): DEBUG True class ProductionConfig(Config): DEBUG False # 生产环境建议使用更安全的数据库如PostgreSQL # SQLALCHEMY_DATABASE_URI os.environ.get(DATABASE_URL)然后在.env文件中切记加入.gitignoreSECRET_KEYyour-super-secret-random-key-here DATABASE_URLsqlite:///app.db在create_app函数中你可以通过环境变量FLASK_ENV来决定加载哪个配置。3. 数据基石用SQLAlchemy设计博客的核心数据模型数据库是博客的“记忆”。设计一个好的模型能让后续的业务逻辑开发事半功倍。对于个人博客最核心的三个模型是用户User、文章Post、评论Comment。它们之间的关系是一个用户写多篇文章一篇文章有多个评论一个评论属于一个用户和一篇文章。3.1 模型定义与关系建立在app/models.py中我们使用SQLAlchemy的ORM来定义它们from datetime import datetime from app import db, login_manager from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash class User(UserMixin, db.Model): id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(64), indexTrue, uniqueTrue, nullableFalse) email db.Column(db.String(120), indexTrue, uniqueTrue, nullableFalse) password_hash db.Column(db.String(128)) # 建立与Post模型的一对多关系backref定义在Post中反向引用的名称 posts db.relationship(Post, backrefauthor, lazydynamic) # 建立与Comment模型的一对多关系 comments db.relationship(Comment, backrefauthor, lazydynamic) def set_password(self, password): # 务必存储哈希值而非明文密码 self.password_hash generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def __repr__(self): return fUser {self.username} class Post(db.Model): id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(140), nullableFalse) body db.Column(db.Text, nullableFalse) # 使用Text类型存储长内容 # 存储生成的文章URL友好标识如“my-first-post” slug db.Column(db.String(140), uniqueTrue, indexTrue) timestamp db.Column(db.DateTime, indexTrue, defaultdatetime.utcnow) # 文章是否发布用于实现草稿功能 published db.Column(db.Boolean, defaultFalse) # 外键指向User表的id字段 user_id db.Column(db.Integer, db.ForeignKey(user.id)) # 建立与Comment模型的一对多关系 comments db.relationship(Comment, backrefpost, lazydynamic, cascadeall, delete-orphan) def __repr__(self): return fPost {self.title} class Comment(db.Model): id db.Column(db.Integer, primary_keyTrue) body db.Column(db.Text, nullableFalse) timestamp db.Column(db.DateTime, indexTrue, defaultdatetime.utcnow) # 评论是否通过审核针对垃圾评论 approved db.Column(db.Boolean, defaultFalse) # 外键指向User表和Post表 user_id db.Column(db.Integer, db.ForeignKey(user.id)) post_id db.Column(db.Integer, db.ForeignKey(post.id)) def __repr__(self): return fComment {self.body[:20]}...3.2 几个关键细节与避坑点密码哈希werkzeug.security提供的generate_password_hash和check_password_hash是标准做法千万不要自己实现加密逻辑。关系中的lazy参数lazydynamic对于一对多关系非常有用。比如user.posts返回的是一个查询对象Query而不是直接加载所有文章列表。这允许你后续进行过滤和分页例如user.posts.filter_by(publishedTrue).order_by(Post.timestamp.desc()).paginate(...)。如果确定关系集合很小使用lazyselect或lazyjoined也可以。级联删除在Post和Comment的关系中我设置了cascadeall, delete-orphan。这意味着当一篇文章被删除时所有关联的评论也会被自动删除。这是一个需要谨慎考虑的设计决策。如果你希望保留评论例如在管理后台查看就不要设置这个级联或者设置为cascadesave-update, merge。Slug的生成与唯一性slug字段用于生成美观的URL如/post/my-first-post。你需要在创建或更新文章时根据标题自动生成一个唯一的slug。可以使用python-slugify库并确保在数据库层面添加唯一索引uniqueTrue在代码层面处理可能的重复例如在重复的slug后追加随机字符串或ID。4. 视图与交互用Jinja2模板和Flask-WTF构建前端界面后端模型建好了接下来就是让用户能看到和交互的界面。这涉及到两部分处理HTTP请求的视图函数View Function和渲染HTML的模板Template。4.1 视图函数与路由设计以博客主蓝图app/blog/routes.py为例我们来实现首页和文章详情页from flask import render_template, request, current_app from app.blog import bp from app.models import Post from app.extensions import db bp.route(/) bp.route(/index) def index(): # 获取页码参数默认为第一页 page request.args.get(page, 1, typeint) # 只查询已发布的文章按时间倒序排列并进行分页 posts Post.query.filter_by(publishedTrue)\ .order_by(Post.timestamp.desc())\ .paginate(pagepage, per_pagecurrent_app.config[POSTS_PER_PAGE], error_outFalse) # paginate对象包含当前页的项目列表posts.items、总页数等 next_url url_for(blog.index, pageposts.next_num) if posts.has_next else None prev_url url_for(blog.index, pageposts.prev_num) if posts.has_prev else None return render_template(blog/index.html, postsposts.items, next_urlnext_url, prev_urlprev_url) bp.route(/post/slug) def post_detail(slug): post Post.query.filter_by(slugslug, publishedTrue).first_or_404() return render_template(blog/post_detail.html, postpost)4.2 Jinja2模板继承与宏Macro的使用为了避免在每个HTML文件中重复写导航栏、页脚等代码Jinja2的模板继承是核心功能。首先创建一个基础模板app/templates/base.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{% block title %}我的博客{% endblock %}/title link relstylesheet href{{ url_for(static, filenamecss/style.css) }} /head body nav !-- 导航栏 -- a href{{ url_for(blog.index) }}首页/a {% if current_user.is_authenticated %} a href{{ url_for(blog.new_post) }}写文章/a a href{{ url_for(auth.logout) }}退出/a {% else %} a href{{ url_for(auth.login) }}登录/a a href{{ url_for(auth.register) }}注册/a {% endif %} /nav main !-- 用于显示Flash消息 -- {% with messages get_flashed_messages() %} {% if messages %} div classalert {% for message in messages %} {{ message }} {% endfor %} /div {% endif %} {% endwith %} !-- 子模板的内容将在这里被插入 -- {% block content %}{% endblock %} /main footer !-- 页脚 -- pcopy; 2023 我的博客/p /footer /body /html然后具体的页面模板如blog/index.html继承它并填充content块{% extends base.html %} {% block title %}首页 - 我的博客{% endblock %} {% block content %} h1最新文章/h1 {% for post in posts %} article classpost-preview h2a href{{ url_for(blog.post_detail, slugpost.slug) }}{{ post.title }}/a/h2 p classmeta由 {{ post.author.username }} 发布于 {{ post.timestamp.strftime(%Y-%m-%d) }}/p p{{ post.body[:150] | striptags }}.../p !-- 截取摘要并过滤HTML标签 -- /article {% endfor %} !-- 分页导航 -- div classpagination {% if prev_url %} a href{{ prev_url }}上一页/a {% endif %} {% if next_url %} a href{{ next_url }}下一页/a {% endif %} /div {% endblock %}对于表单渲染这种重复性高的代码可以使用Jinja2的宏。在app/templates/macros.html中定义一个渲染表单字段的宏{% macro render_field(field) %} div classform-group {{ field.label }} {{ field(class_form-control, **kwargs) }} {% if field.errors %} ul classerrors {% for error in field.errors %} li{{ error }}/li {% endfor %} /ul {% endif %} /div {% endmacro %}然后在模板中导入并使用它能让表单代码简洁很多。4.3 使用Flask-WTF处理表单与验证表单是博客交互的关键登录、注册、发布文章、评论。Flask-WTF集成了WTForms提供了强大的表单生成、CSRF保护和数据验证功能。以发布文章的表单为例app/blog/forms.pyfrom flask_wtf import FlaskForm from wtforms import StringField, TextAreaField, BooleanField, SubmitField from wtforms.validators import DataRequired, Length from app.models import Post class PostForm(FlaskForm): title StringField(标题, validators[DataRequired(), Length(max140)]) body TextAreaField(内容, validators[DataRequired()]) slug StringField(URL标识符Slug, validators[Length(max140)]) published BooleanField(立即发布) submit SubmitField(发布)在视图函数中处理这个表单from flask import render_template, redirect, url_for, flash from flask_login import login_required, current_user from app.blog.forms import PostForm bp.route(/new_post, methods[GET, POST]) login_required def new_post(): form PostForm() if form.validate_on_submit(): # 处理Slug如果用户没填则根据标题自动生成 slug form.slug.data if not slug: from slugify import slugify slug slugify(form.title.data) # 确保Slug唯一 counter 1 original_slug slug while Post.query.filter_by(slugslug).first() is not None: slug f{original_slug}-{counter} counter 1 post Post( titleform.title.data, bodyform.body.data, slugslug, publishedform.published.data, authorcurrent_user ) db.session.add(post) db.session.commit() flash(文章已发布) return redirect(url_for(blog.post_detail, slugpost.slug)) return render_template(blog/edit_post.html, formform)这里有几个关键点1)login_required装饰器确保只有登录用户才能访问。2)form.validate_on_submit()会在POST请求且表单数据验证通过时返回True。3) 我们手动处理了Slug的生成和唯一性逻辑。4) 使用flash()函数给用户反馈操作结果这个消息会在下一次请求时通过模板中的get_flashed_messages()显示出来。5. 用户认证与权限控制Flask-Login实战详解没有用户系统博客的“个人”属性就无从谈起。Flask-Login是处理用户会话和登录状态的利器但它只负责“登录状态”的管理不处理具体的注册、密码验证逻辑这部分需要我们自己实现。5.1 初始化与用户加载器首先在app/extensions.py中初始化LoginManagerfrom flask_login import LoginManager login_manager LoginManager() login_manager.login_view auth.login # 指定未登录用户重定向的登录页面端点 login_manager.login_message 请先登录以访问此页面。 login_manager.login_message_category info在app/__init__.py的工厂函数中像之前一样用login_manager.init_app(app)初始化它。最关键的一步是定义用户加载器user_loader。这个回调函数告诉Flask-Login如何根据存储在用户会话中的用户ID重新加载用户对象。它必须放在models.py或能被工厂函数导入的地方from app import login_manager login_manager.user_loader def load_user(id): # 因为User模型继承了UserMixinFlask-Login期望这里返回User对象或None return User.query.get(int(id))5.2 实现登录与注册视图在认证蓝图app/auth/routes.py中from flask import render_template, redirect, url_for, flash, request from flask_login import login_user, logout_user, current_user, login_required from app.auth import bp from app.auth.forms import LoginForm, RegistrationForm from app.models import User from app.extensions import db bp.route(/login, methods[GET, POST]) def login(): # 如果用户已经登录直接跳转到首页 if current_user.is_authenticated: return redirect(url_for(blog.index)) form LoginForm() if form.validate_on_submit(): # 根据用户名或邮箱查找用户 user User.query.filter((User.username form.username_or_email.data) | (User.email form.username_or_email.data)).first() # 验证用户存在且密码正确 if user is None or not user.check_password(form.password.data): flash(无效的用户名或密码, danger) return redirect(url_for(auth.login)) # 调用login_user将用户登录状态存入会话 login_user(user, rememberform.remember_me.data) # 处理“next”参数登录后跳转到用户原本想访问的页面 next_page request.args.get(next) if not next_page or not next_page.startswith(/): next_page url_for(blog.index) return redirect(next_page) return render_template(auth/login.html, formform) bp.route(/register, methods[GET, POST]) def register(): if current_user.is_authenticated: return redirect(url_for(blog.index)) form RegistrationForm() if form.validate_on_submit(): # 检查用户名和邮箱是否已存在 user_by_name User.query.filter_by(usernameform.username.data).first() user_by_email User.query.filter_by(emailform.email.data).first() if user_by_name: flash(该用户名已被注册, danger) return redirect(url_for(auth.register)) if user_by_email: flash(该邮箱已被注册, danger) return redirect(url_for(auth.register)) # 创建新用户 user User(usernameform.username.data, emailform.email.data) user.set_password(form.password.data) db.session.add(user) db.session.commit() flash(注册成功请登录。, success) return redirect(url_for(auth.login)) return render_template(auth/register.html, formform) bp.route(/logout) login_required def logout(): logout_user() flash(您已成功退出登录。, info) return redirect(url_for(blog.index))5.3 权限控制的进阶自定义装饰器Flask-Login的login_required只能判断用户是否登录。如果我们想实现更细粒度的权限控制比如只有管理员才能访问后台就需要自定义装饰器。例如在app/decorators.py中from functools import wraps from flask import abort, flash, redirect, url_for from flask_login import current_user def admin_required(f): wraps(f) def decorated_function(*args, **kwargs): # 假设User模型有一个is_admin布尔字段 if not current_user.is_authenticated or not current_user.is_admin: flash(此操作需要管理员权限。, danger) return redirect(url_for(blog.index)) return f(*args, **kwargs) return decorated_function然后在后台管理的视图函数上使用admin_required即可。6. 部署上线从开发服务器到生产环境的完整迁移在本地flask run跑起来只是第一步。要让博客在公网可访问你需要将其部署到生产环境。这里我以最经典的“Nginx Gunicorn Supervisor”在Linux服务器如Ubuntu上的部署方案为例。6.1 生产环境依赖与WSGI服务器首先更新你的requirements.txt确保包含生产环境需要的包。除了Flask及其扩展关键的是WSGI服务器我们选择Gunicorn。Flask2.3.3 Flask-SQLAlchemy3.0.5 Flask-Login0.6.2 Flask-WTF1.1.1 Flask-Migrate4.0.4 python-dotenv1.0.0 Werkzeug2.3.6 Jinja23.1.2 SQLAlchemy2.0.19 greenlet2.0.2 gunicorn21.2.0 psycopg2-binary2.9.7 # 如果使用PostgreSQL在服务器上克隆你的代码创建虚拟环境并安装依赖。6.2 使用Gunicorn启动应用Flask自带的开发服务器性能差、不安全绝不能用于生产。Gunicorn是一个纯Python的WSGI HTTP服务器稳定高效。在项目根目录下创建一个wsgi.py文件作为Gunicorn的入口点from app import create_app app create_app() if __name__ __main__: app.run()然后使用Gunicorn启动应用gunicorn -w 4 -b 127.0.0.1:8000 wsgi:app-w 4: 启动4个工作进程通常建议为CPU核心数*21。-b 127.0.0.1:8000: 绑定到本地的8000端口。我们不直接对外暴露Gunicorn而是通过Nginx反向代理。wsgi:app:wsgi是模块名即wsgi.pyapp是其中可调用的Flask应用实例。6.3 使用Supervisor管理进程我们需要一个进程管理工具来确保Gunicorn在服务器重启后能自动运行并在崩溃时自动重启。Supervisor是很好的选择。安装Supervisor后创建一个配置文件/etc/supervisor/conf.d/myblog.conf[program:myblog] command/path/to/your/venv/bin/gunicorn -w 4 -b 127.0.0.1:8000 wsgi:app directory/path/to/your/myblog userwww-data ; 运行进程的用户根据你的服务器用户设置 autostarttrue autorestarttrue stopasgrouptrue killasgrouptrue stderr_logfile/var/log/myblog/err.log stdout_logfile/var/log/myblog/out.log然后更新Supervisor并启动你的应用sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start myblog6.4 配置Nginx作为反向代理Nginx负责处理静态文件效率远高于Python进程并将动态请求转发给后端的Gunicorn。创建一个Nginx站点配置文件/etc/nginx/sites-available/myblogserver { listen 80; server_name yourdomain.com www.yourdomain.com; # 你的域名 location / { proxy_pass http://127.0.0.1:8000; # 转发给Gunicorn proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /static { alias /path/to/your/myblog/app/static; # 静态文件直接由Nginx处理 expires 30d; access_log off; } }创建软链接启用该配置并测试、重载Nginxsudo ln -s /etc/nginx/sites-available/myblog /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx6.5 生产环境关键配置调整最后别忘了调整Flask应用本身的配置app/config.py中的ProductionConfigclass ProductionConfig(Config): DEBUG False # 生产环境务必使用强密钥并通过环境变量设置 SECRET_KEY os.environ.get(SECRET_KEY) # 使用PostgreSQL等生产级数据库 SQLALCHEMY_DATABASE_URI os.environ.get(DATABASE_URL) # 确保在代理服务器后运行以便request.is_secure等能正确工作 PREFERRED_URL_SCHEME https # 如果你使用了HTTPS在服务器上设置相应的环境变量如SECRET_KEY,DATABASE_URL,FLASK_ENVproduction。7. 进阶与优化让博客系统更健壮、更易用基础功能跑通后你可以考虑以下优化点来提升博客的体验和可维护性。7.1 实现全文搜索SQLite/MySQL的LIKE查询效率低下且功能弱。集成一个轻量级的全文搜索引擎如Whoosh或Elasticsearch是更好的选择。对于个人博客Whoosh是一个纯Python实现、无需外部服务的选择。你可以创建一个app/search.py模块在文章创建或更新时索引内容并提供搜索视图。7.2 添加Markdown支持让用户和你自己用Markdown写作远比用富文本编辑器或纯HTML友好。集成Flask-PageDown前端和Markdown/mistune后端渲染可以轻松实现。在存储文章内容时同时存储原始的Markdown文本和渲染后的HTML前者用于编辑后者用于展示。7.3 文件上传与图片管理允许在文章中插入图片是刚需。使用Flask-Uploads或直接处理request.files可以实现上传功能。关键点在于限制文件类型如只允许jpg,png,gif和大小。对上传的文件进行重命名如使用UUID避免文件名冲突和安全问题。指定一个非Web根目录的子目录来存放上传的文件并通过一个特定的路由如/uploads/filename来提供访问而不是直接暴露静态文件目录。7.4 缓存策略对于首页、文章详情页这些不常变化的内容引入缓存能显著提升响应速度。Flask-Caching扩展支持多种后端如Redis、Memcached、文件系统。一个简单的策略是为index和post_detail视图函数添加缓存装饰器设置一个合理的过期时间如5分钟。7.5 日志记录生产环境必须要有完善的日志记录。Flask内置了基于Pythonlogging模块的日志系统。在工厂函数中配置日志将不同级别的日志INFO, ERROR输出到文件便于故障排查。import logging from logging.handlers import RotatingFileHandler def create_app(config_classConfig): # ... 之前的初始化代码 ... if not app.debug and not app.testing: # 生产环境日志配置 if not os.path.exists(logs): os.mkdir(logs) file_handler RotatingFileHandler(logs/myblog.log, maxBytes10240, backupCount10) file_handler.setFormatter(logging.Formatter( %(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d] )) file_handler.setLevel(logging.INFO) app.logger.addHandler(file_handler) app.logger.setLevel(logging.INFO) app.logger.info(MyBlog startup) # ...从零开始用Flask搭建博客是一个典型的“全栈”练手项目。它强迫你去思考前后端的交互、数据流、安全性和部署运维。过程中你肯定会遇到各种坑比如数据库连接池配置、静态文件缓存、CSRF保护、XSS防御等。但每解决一个问题你对Web开发的理解就更深一层。这个博客系统完全可以作为你未来更复杂项目的基石或者它本身就是一个值得你长期维护和打磨的、完全属于你的数字家园。本文还有配套的精品资源点击获取