# blog **Repository Path**: wizen/blog ## Basic Information - **Project Name**: blog - **Description**: 基于Flask的博客系统 - **Primary Language**: Python - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 2 - **Created**: 2024-06-05 - **Last Updated**: 2025-04-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 基于Flask的博客系统 ## 总体介绍 #### 1. 系统分析 用例分析图:
![](src/BlogUML.png)
#### 2. 数据库表 系统E-R图:
![](src/BlogER.png)
## Flask入门 #### 1. 安装Flask框架 ```powershell pip install flask ``` #### 2. Flask目录说明 创建Flask应用后,会有三个`static`,`templates`,`app.py`: * static:这个目录用于存放所有的静态文件,比如 CSS、JavaScript、图像等。 * templates:存放应用的模板文件,通常是 HTML 文件,但也可以是 Jinja2 模板引擎支持的其他格式。 * app.py:这是 Flask 应用的主要入口文件。 #### 3. 运行第一个网站 * `app.py` ```python # 导入 Flask 类,这是构建 Flask 应用程序的基础 from flask import Flask # 创建 Flask 应用实例,__name__ 作为参数传入,用于定位资源文件(如模板和静态文件) app = Flask(__name__) # 使用 @app.route 装饰器定义路由,此处 '/' 表示应用的根 URL @app.route('/') # 定义一个名为 hello_world 的视图函数,当访问应用根 URL 时被调用 def hello_world(): # put application's code here # 函数返回简单的字符串 "Hello World!" 作为响应内容 return 'Hello World!' # 检查是否直接运行此脚本(而非作为模块导入),如果是,则启动 Flask 开发服务器 if __name__ == '__main__': # 调用 app.run() 方法启动 Web 服务器,默认会在本地的 5000 端口运行 app.run() ``` * run启动参数详解 在 Flask 中,`app.run()` 方法用于启动开发服务器,默认情况下,它会快速启动一个服务器供开发和测试使用。`app.run()` 方法接受多个参数来定制服务器的行为,以下是一些常用的参数及其说明: 1. `host (string)`:指定服务器监听的 IP 地址。默认值是 `'127.0.0.1'`,这意味着服务器只接受来自本地的连接请求。若要让服务器公开可由外部访问,可以设置为 `'0.0.0.0'`。 2. `port (int)`:指定服务器监听的端口号。默认值是 `5000`。可以根据需要更改端口,例如,如果默认端口已被占用,可以设置为 `8000`。 3. `debug (bool)`:开启或关闭调试模式。默认是 `False`。如果设置为 `True`,服务器将在代码修改后自动重载,并在遇到错误时提供详细的堆栈跟踪信息,这对于开发阶段非常有用,但出于安全考虑,生产环境中应设为 `False`。 4. `use_reloader (bool)`:默认为 `True`,当 `debug=True` 时,自动重启服务器以便于代码更改生效。如果在某些复杂环境下(比如使用了多进程或多线程),可能需要关闭重载器,设为 `False`。 5. `threaded (bool)`:是否使用多线程模式。默认是 `False`,意味着服务器在接收到请求时会阻塞直到处理完该请求。设置为 `True` 允许服务器同时处理多个请求,这对于并发请求处理很有帮助。 6. `ssl_context (None 或 tuple)`:如果需要使用 HTTPS,可以通过这个参数指定 SSL/TLS 上下文。通常是一个包含证书和私钥文件路径的元组,如 `('cert.pem', 'key.pem')`。 #### 4. 示例参数代码 ```python if __name__ == '__main__': app.run( host='0.0.0.0', # 允许外部访问 port=8000, # 更改默认端口为8000 debug=True, # 开启调试模式 threaded=True, # 启用多线程 use_reloader=False # 假设在某种特殊环境下,关闭自动重载 ) ``` > 通过调整这些参数,你可以根据实际需求定制 Flask 开发服务器的行为。但需要注意的是,`app.run()` 用于开发环境,对于生产部署,推荐使用更强大的 `WSGI` 服务器,如 `Gunicorn` 或 `uWSGI`,配合 `Nginx` 等反向代理服务器,以提高性能和安全性。 ## 注册与登录 #### 1. 下载 layui 文件 访问 https://layui.dev/ ,点击直接下载之后就能得到 `layui` 的静态文件,将其复制到 `static` 目录下,完成之后的目录结构如下: ```text │ app.py ├─static │ │ layui.js │ ├─css │ │ layui.css │ └─font │ iconfont.eot │ iconfont.svg │ iconfont.ttf │ iconfont.woff │ iconfont.woff2 └─templates ``` #### 2. 编写静态页面 * 复制 https://layui.dev/docs/2.8/layout/ 的内容进行修改。完成 index.html (首页)的制作。 * 复制 https://layui.dev/docs/2.8/form/#reg 修改为注册页 * 复制 https://layui.dev/docs/2.8/form/#login 修改为登录页 #### 3. 修改静态页面 在`templates`目录下编写前端首页(`index.html`)、注册页(`register.html`)、登录页(`login.html`)。 * index.html ```html 首页
``` * register.html ```html 注册页
登录已有帐号
``` * login.html ```html 登录页
``` #### 4. 编写后端路由 前端编写好了之后,实现前后端基本的跳转,需要适配后端路由才能实现访问。 * app.py ```python from flask import Flask, render_template app = Flask(__name__) @app.route('/') def index_view(): return render_template('index.html') @app.route('/register') def register_view(): return render_template('register.html') @app.route('/login') def login_view(): return render_template('login.html') if __name__ == '__main__': app.run(debug=True) ``` > 后端路由编写好了之后,如果想要实现联调,就需要修改前端页面的逻辑。 1. 没有登录之前不显示个人信息,只显示左上角的登录按钮,点击登录按钮调整到登录页面。 2. 登录是没有账号,就先进行注册。注册页面如果有账号就跳转到登录页面。 3. 注册账号成功之后跳转到登录页面 #### 5. 实现注册逻辑 注册功能逻辑时序图: ![](src/register.png) 在实现逻辑之前,先完善一下前端跳转页面 1. 没有登录之前不显示个人信息,只显示左上角的登录按钮,修改 index.html 的内容。 ```html ``` 2. 登录是没有账号,就先进行注册。注册页面如果有账号就跳转到登录页面。 * 修改 login.html ```html 注册帐号 ``` * 修改 register.html ```html 登录已有帐号 ``` 3. 前端校验手机号,输入手机号之后进行校验: ```javascript ``` > 点击获取验证码之后,就会发送请求到后端,然后后端处理完之后返回结果给前端。 4. 后端发送短信验证码,后端获取数据之后 * 解析前端传递过来的手机号,然后进行校验 * 调用第三方工具下发注册短信验证码(腾讯云、阿里云就有对于的服务) * 将手机号、短信验证码记录到数据库。下次提交时再进行校验 ```python import logging import re import random from flask import Flask, render_template, request, session app = Flask(__name__) app.secret_key = 'hnkjzydx.xxgcxy' @app.post('/api/send_register_sms') def send_register_sms(): # 1. 解析前端传递过来的数据 data = request.get_json() mobile = data['mobile'] # 2. 校验手机号码 pattern = r'^1[3-9]\d{9}$' ret = re.match(pattern, mobile) if not ret: return { 'message': '电话号码不符合格式', 'code': -1 } # 3. 发送短信验证码,并记录 session['mobile'] = mobile # 3.1 生成随机验证码 code = random.choices('123456789', k=6) session['code'] = ''.join(code) logging.warning(code) return { 'message': '发送短信成功', 'code': 0 } ``` > 因为是一个简单的案例,就没有调用腾讯云短信进行测试了,而是直接把正确结果返回。对于短信与验证码也没有存储到数据库了,而是放在 `session` 里面进行返回,下次请求注册时,再从里面进行提取。 5. 前端部分 在手机收到短信验证码之后,前端填入验证码,然后再输入两次密码,点击同意协议按钮,就能实现注册。在这同时需要做验证验证码与密码。`register.html` 中添加`注册`按钮提交事件的 `js` 代码: ```javascript ``` 6. 后端逻辑 小案例,数据库使用 sqlite 是最方便的。 * db.py ```python import sqlite3 class Database: def __init__(self): # 连接数据库 self.conn = sqlite3.connect('flask-layui.sqlite') # 设置数据的解析方法,有了这个设置,就可以像字典一样访问每一列数据 self.conn.row_factory = sqlite3.Row # connect('服务器名称', '用户名', '密码', '库名') # import pymysql #MySQL # self.conn = pymysql.connect(host='localhost', user='root', password='root', database='School', charset='utf8') # import pymssql #SQLServer # self.conn = pymssql.connect('(local)','sa','123456','School') # 创建游标对象 self.cursor = self.conn.cursor() def create_table(self): sql = """create table user ( id integer primary key, nickname varchar(255), mobile varchar(50), password varchar(50) ); """ self.cursor.execute(sql) self.conn.commit() def insert(self, nickname, mobile, password): sql = 'insert into user(nickname, mobile, password) values (?, ?, ?);' self.cursor.execute(sql, (nickname, mobile, password)) self.conn.commit() def search(self, mobile): sql = 'select * from user where mobile=?;' self.cursor.execute(sql, (mobile,)) return self.cursor.fetchone() db = Database() if __name__ == '__main__': # db.create_table() ## 创建数据库表,若已经创建好数据库表可注释该行,以免重复创建报错 # db.insert('博主', '13838383838', '123456') ret = db.search('13838383838') print(ret['password']) # 若希望密码在数据库中非明文显示,可采用如下MD5加密函数,注意密码验证时对比加密后的字符 import hashlib def md5_encryption(data): md5 = hashlib.md5() # 创建一个md5对象 md5.update(data.encode('utf-8')) # 使用utf-8编码数据 return md5.hexdigest() # 返回加密后的十六进制字符串 print(md5_encryption(ret['password'])) ``` * app.py ```python from db import Database @app.post('/api/register') def register_api(): # 1. 解析前端传递过来的数据 data = request.get_json() print(data) # vercode = data['vercode'] # vercode2 = session['code'] # if vercode != vercode2: # return { # 'message': '短信验证码错误', # 'code': -1 # } nickname = data['nickname'] mobile = data['mobile'] password = data['password'] if not all([nickname, mobile, password]): return { 'message': '数据缺失', 'code': -1 } Database().insert(nickname, mobile, password) return { 'message': '注册用户成功', 'code': 0 } ``` #### 6. 实现登录逻辑 登录功能逻辑时序图: ![](src/login.png) 1. 图片验证码 * 客户端请求服务器获得图片验证码 * 服务器返回图片,将生成的验证码保存到数据库 * 客户端携带验证码进行请求,服务器校验数据 * 登录成功之后跳转到首页,登录失败则重新登录 2. 前端发送请求,生成 uuid 用于记录验证码 ```html ``` 3. 图片验证码更新 ```html ``` 4. 后端生成图片验证码 * 安装依赖: ```powershell pip install captcha ``` * 然后编写后端的视图 `get_captcha.py` ```python # filename: /get_captcha.py from io import BytesIO from random import choices from captcha.image import ImageCaptcha from flask import make_response from PIL import Image def gen_captcha(content="0123456789"): """生成验证码""" image = ImageCaptcha() # 获取字符串 captcha_text = "".join(choices(content, k=4)) # 生成图像 captcha_image = Image.open(image.generate(captcha_text)) return captcha_text, captcha_image # 生成验证码 def get_captcha_code_and_content(): code, image = gen_captcha() out = BytesIO() image.save(out, "png") out.seek(0) content = out.read() # 读取图片的二进制数据做成响应体 return code, content if __name__ == '__main__': code, content = get_captcha_code_and_content() print(code, content) ``` * app.py ```python from get_captcha import get_captcha_code_and_content @app.get('/get_captcha') def get_captcha_view(): # 1. 获取参数 captcha_uuid = request.args.get("captcha_uuid") # 2. 生成验证码 code, content = get_captcha_code_and_content() # 3. 记录数据到数据库(用session代替) session['code'] = code resp = make_response(content) # 读取图片的二进制数据做成响应体 resp.content_type = "image/png" # 4. 错误处理 # 5. 响应返回 return resp ``` 验证码图片地址: ```html ``` 5. 前端请求进行登录 ```javascript ``` 6. 后端验证登录 * 编写登录接口 `app.py` ```python @app.post('/api/login') def login_api(): data = request.get_json() ret = Database().search(data['mobile']) code = session['code'] if code != data['captcha']: return { 'message': '验证码错误', 'code': -1 } if not ret: return { 'message': '用户不存在', 'code': -1 } pwd = ret['password'] if pwd != data['password']: return { 'message': '用户密码错误', 'code': -1 } session['_user_id'] = ret['id'] # 记录用户登录id return { 'message': '用户登录成功', 'code': 0 } ``` #### 7. 装饰器权限拦截 * 登录拦截 ```python # filename: app.py from functools import wraps from flask import g, redirect def login_required(func): @wraps(func) def wrapper(*args, **kwargs): user_id = session.get('_user_id', 0) user = Database().search_uid(user_id) if not user: return redirect('/login') g.user = user result = func(*args, **kwargs) return result return wrapper ``` * 数据库添加按用户 `id` 查找方法 ```python # filename: db.py def search_uid(self, uid): sql = 'select nickname from user where id=?;' self.cursor.execute(sql, (uid,)) return self.cursor.fetchone() ``` * 用户名 `index.html` 渲染 ```html {% if not g.user %} 登录 {% else %} {{ g.user['nickname'] }}
个人中心
设置
退出
{% endif %} ``` #### 8. 退出登录 * 前端链接 ```html
退出
``` * 后台逻辑 ```python @app.route('/logout') def logout(): session.clear() # 清除所有session数据 return redirect('/login') ``` ## 博客系统 #### 1. 创建数据库表 * 创建 `blog` 数据库表并添加两条数据 ```python class Database: # ...... def create_blog(self): sql = """create table blog ( id integer primary key, uid integer(32), created timestamp not null default current_timestamp, title text not null , content text not null ); """ self.cursor.execute(sql) self.conn.commit() def insert_blog(self, uid, title, content): sql = 'insert into blog(uid, title, content) values (?, ?, ?);' self.cursor.execute(sql, (uid, title, content)) self.conn.commit() db = Database() if __name__ == '__main__': # ...... db.create_blog() # 创建数据库表,若已经创建好数据库表可注释该行,以免重复创建报错 db.insert_blog(1, '学习Flask1', 'Python课程设计学习flask第一部分') # 添加数据1 db.insert_blog(2, '学习Flask2', 'Python课程设计学习flask第二部分') # 添加数据2 ``` #### 2. 创建页面模板 一个Web网站的多个网页中往往包含一些通用内容和样式,例如,导航栏、标题、页脚等,为了避免在多个模板重复编写通用内容和样式的代码,提高代码的重用率,重命名 `index.html` 为基模板 `base.html`。 * 修改`base.html`模板文件中引用静态文件 ```html ``` 1. 为了能够在模板文件中引用静态文件,需要使用`url_for()`函数解析静态文件的`URL`。 2. 静态文件的`URL`规则默认为`/static/`。 3. url_for()函数需要接收两个参数:第1个参数表示端点名称,默认值为`static`;第2个参数`filename`表示静态文件的名称。 * 基模板添加定义块 ```html {% block title %} {% endblock %} ......
{% block content %} {% endblock %}
``` #### 3. 展示博文列表 * `templates` 目录下添加子模版 `index.html` ```html {% extends 'base.html' %} {% block content %}

{% block title %} 欢迎来到{{ g.user['nickname'] }}的博客 {% endblock %}


{% for post in posts %}

{{ post['title'] }}

发布时间:{{ post['created'] }} 编辑
{% endfor %} {% endblock %} ``` * `app.py`路由中添加`index.html`页面渲染 ```python # filename: app.py @app.route('/') @login_required def index_view(): user_id = session.get('_user_id', 0) # 查询所有数据,放到变量posts中 posts = Database().search_blogs(user_id) # 把查询出来的posts传给网页 return render_template('index.html', posts=posts) # ``` * 添加数据库按 `uid` 查询博文 `SQL` 语句 ```python # filename:db.py def search_blogs(self, uid): sql = 'select * from blog where uid=?;' self.cursor.execute(sql,(uid,) ) return self.cursor.fetchall() ``` * `base.html`模板文件中添加`index_view()`引用 ```html
  • 我的博客
  • ``` * `base.html`模板文件中添加博文列表显示样式 ```html ``` #### 4. 新建博文表单 * `templates` 目录下添加子模版 `new.html` ```html {% extends 'base.html' %} {% block content %}

    {% block title %} 新建博文 {% endblock %}


    {% endblock %} ``` #### 5. 新建博文保存 * `app.py`路由中添加`new.html`页面渲染 ```python # filename:app.py from flask import flash, url_for @app.route('/posts/new', methods=('GET', 'POST')) @login_required def new(): if request.method == 'POST': title = request.form['title'] content = request.form['content'] if not title: flash('标题不能为空!') elif not content: flash('内容不能为空') else: user_id = session.get('_user_id', 0) # 插入新内容 Database().insert_blog(user_id, title, content) return redirect(url_for('index_view')) return render_template('new.html') ``` * 添加数据库新建博文 `SQL` 语句 ```python # filename:db.py def insert_blog(self, uid, title, content): sql = 'insert into blog(uid, title, content) values (?, ?, ?);' self.cursor.execute(sql, (uid, title, content)) self.conn.commit() ``` * `base.html`模板文件中添加`new()`路径引用 ```html
  • 发布文章
  • ``` #### 6. 新建博文提示 * 当输入标题为空时,`base.html`模板文件中添加`flash`消息闪现及样式 ```html ......
    {% for message in get_flashed_messages() %}
    {{ message }}
    {% endfor %} {% block content %} {% endblock %}
    ``` #### 7. 博文详情页 * `templates` 目录下添加子模版 `detail.html` ```html {% extends 'base.html' %} {% block content %}

    {% block title %} {{ detail['title'] }} {% endblock %}

    发布时间:{{ detail['created'] }}

    {{ detail['content'] }}


    {% endblock %} ``` * `app.py` 路由中添加 `detail.html` 页面渲染 ```python # filename:app.py @app.route('/posts/') @login_required def detail(post_id): detail = Database().search_bid(post_id) return render_template('detail.html', detail=detail) ``` * 添加数据库按 `id` 查询博文 `SQL` 语句 ```python # filename:db.py def search_bid(self, bid): sql = 'select * from blog where id=?;' self.cursor.execute(sql, (bid,)) return self.cursor.fetchone() ``` * `index.html` 模板文件中添加`detail(post_id)`路径引用 ```html

    {{ post['title'] }}

    ``` #### 8. 删除博文 * `app.py` 路由中添加 `delete` 页面渲染 ```python # filename:db.py @app.route('/posts//delete', methods=('POST',)) @login_required def delete(id): post = Database().search_bid(id) Database().delete_bid(id) flash('"{}" 删除成功!'.format(post['title'])) return redirect(url_for('index_view')) ``` * 添加数据库删除博文 `SQL` 语句 ```python # filename:db.py def delete_bid(self, bid): sql = 'delete from blog where id=?;' self.cursor.execute(sql, (bid,)) self.conn.commit() ``` * `detail.html` 模板文件中添加`delete(id)`路径引用 ```html
    ``` #### 9. 博文编辑页 * `templates` 目录下添加子模版 `edit.html` ```html {% extends 'base.html' %} {% block content %}

    {% block title %} 编辑 "{{ post['title'] }}" {% endblock %}



    {% endblock %} ``` * `app.py` 路由中添加 `edit.html` 页面渲染 ```python # filename:db.py @app.route('/posts//edit', methods=('GET', 'POST')) @login_required def edit(id): post = Database().search_bid(id) if request.method == 'POST': title = request.form['title'] content = request.form['content'] if not title: flash('标题不能为空!') else: Database().update_bid(title, content, id) return redirect(url_for('index_view')) return render_template('edit.html', post=post) ``` * 添加数据库按 `id` 更新博文 `SQL` 语句 ```python # filename:db.py def update_bid(self, title, content, bid): sql = 'update blog set title=?, content=? where id=?;' self.cursor.execute(sql, (title, content, bid)) self.conn.commit() ``` * `index.html` 模板文件中添加`edit(id)`路径引用 ```html 编辑 ``` #### 10. 关于 * `templates` 目录下添加子模版 `about.html` ```html {% extends 'base.html' %} {% block content %}

    {% block title %} Flask简介 {% endblock %}


    Flask是一个使用Python编写的轻量级Web应用框架。它基于Werkzeug WSGI工具包和Jinja2模板引擎,旨在提供一个简单而灵活的核心,让开发者能够快速构建各类Web应用。Flask不会对特定的数据库、表单验证或任何其他功能做强制要求,而是提供了可自由选择的扩展支持,这使得开发者能够根据项目需求,高度自定义应用结构和功能。

    {% endblock %} ``` * `app.py` 路由中添加 `about.html` 页面渲染 ```python # filename:db.py @app.route('/about') @login_required def about(): return render_template('about.html') ``` * `base.html` 模板文件中添加`about()`路径引用 ```html
  • 关于
  • ```