# 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. 系统分析
用例分析图:

#### 2. 数据库表
系统E-R图: 
## 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. 实现注册逻辑
注册功能逻辑时序图:

在实现逻辑之前,先完善一下前端跳转页面
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. 实现登录逻辑
登录功能逻辑时序图:

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
关于
```