留言板开发
一、相关技术介绍
本项目基于现代Web开发技术栈构建,采用Python 3.12作为后端开发语言,搭配轻量级Flask 2.3.2框架实现核心功能。数据库层使用Flask-SQLAlchemy ORM框架操作MySQL 8.0数据库,通过对象关系映射简化数据操作。用户认证模块整合Flask-Login实现安全的会话管理,结合Flask-WTF处理表单验证,确保数据输入的合法性。前端界面采用Bootstrap 5框架构建响应式布局,通过Jinja2模板引擎实现动态内容渲染,整体架构遵循MVT(Model-View-Template)设计模式,实现业务逻辑、数据管理和表现层的清晰分离。详细如下
Python 3.12:作为后端编程语言
Flask 2.3.2:轻量级Web框架
Flask-SQLAlchemy:ORM框架,用于数据库操作
MySQL 8.0:关系型数据库存储数据
Flask-Login:用户认证管理
Flask-WTF:表单处理和验证
Bootstrap 5:前端UI框架
Jinja2:模板引擎
MVT设计模式:Model-View-Template架构
二、项目整体框架
1、代码目录结构
pythonProject/
├── venv/ # PyCharm自动创建的虚拟环境
├── app/ # 应用主目录
│ ├── __init__.py # 应用初始化文件
│ ├── auth/ # 认证蓝图
│ │ ├── __init__.py
│ │ ├── forms.py
│ │ └── views.py
│ ├── main/ # 主蓝图
│ │ ├── __init__.py
│ │ ├── forms.py
│ │ └── views.py
│ ├── models.py # 数据模型
│ └── templates/ # 模板文件
│ ├── auth/
│ │ ├── login.html
│ │ └── register.html
│ ── main/
│ ├── base.html
│ ├── create_message.html
│ ├── edit_message.html
│ ├── message_detail.html
│ └── messages.html
├── config.py # 配置文件
├── requirements.txt # 依赖文件
├── run.py # 启动文件
└── migrations/ # 数据库迁移文件夹(后续生成)
2、项目配置文件(config.py)
import 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 'dev-key-123'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'mysql+pymysql://username:password@localhost/flask_data'
SQLALCHEMY_TRACK_MODIFICATIONS = False
MESSAGES_PER_PAGE = 5
3、项目扩展包文件(requirements.txt)
Flask==2.3.2
Flask-SQLAlchemy==3.0.3
Flask-Login==0.6.2
Flask-WTF==1.1.1
Flask-Migrate==4.0.4
PyMySQL==1.0.3
python-dotenv==1.0.0
email-validator==2.0.0
三、数据库设计
1、对于数据库上的操作
检查MySQL服务状态
sudo systemctl status mysql
如果未运行,启动它
sudo systemctl start mysql
2、在mysql中创建数据库flask_data
CREATE DATABASE flask_data CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
3、数据库连接
CREATE DATABASE flask_data CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON flask_data.* TO 'root'@'%' IDENTIFIED BY '12345678';
FLUSH PRIVILEGES;
EXIT;
在pycharm终端执行
flask db init
flask db migrate -m "initial migration"
flask db upgrade
4、数据库表设计
App/models.py 核心代码
from datetime import datetime
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from app import db, login
class User(UserMixin, db.Model):
"""用户表"""
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(255)) # 加密后的密码
messages = db.relationship('Message', backref='author', lazy='dynamic')
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)
class Message(db.Model):
__tablename__ = 'messages'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
三线表描述
- users表结构
字段名 |
类型 |
说明 |
id |
INT |
主键,自增 |
username |
VARCHAR(64) |
用户名,唯一 |
VARCHAR(120) |
邮箱,唯一 |
|
password_hash |
VARCHAR(255) |
加密后的密码 |
created_at |
DATETIME |
创建时间 |
- messages表结构
字段名 |
类型 |
说明 |
id |
INT |
主键,自增 |
title |
VARCHAR(100) |
留言标题 |
content |
TEXT |
留言内容 |
user_id |
INT |
外键,关联users.id |
created_at |
DATETIME |
创建时间 |
updated_at |
DATETIME |
最后更新时间 |
四、留言板模块设计
1、登陆模块
功能组成:登录模块提供完整的用户认证流程,包含四大核心功能:首先,用户注册功能实现账户创建,采用密码加密存储确保安全性;其次,登录功能支持凭证验证和会话管理;第三,退出功能可安全终止用户会话;最后,"记住我"选项通过持久化Cookie提升用户体验。所有密码均通过加密算法处理后再存储,即使数据库泄露也不会暴露原始密码。
核心代码 (auth/views.py):
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, current_user
from app import db
from app.auth import bp
from app.auth.forms import LoginForm, RegistrationForm
from app.models import User
@bp.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('无效的用户名或密码')
return redirect(url_for('auth.login'))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('main.index'))
return render_template('auth/login.html', title='登录', form=form)
@bp.route('/logout')
def logout():
print(f"退出前用户: {current_user}") # 调试信息
logout_user()
print(f"退出后用户: {current_user}") # 调试信息
return redirect(url_for('main.index'))
@bp.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('恭喜,注册成功!')
return redirect(url_for('auth.login'))
return render_template('auth/register.html', title='注册', form=form)
2、留言板模块
功能组成:留言板模块构成应用的核心功能体系:分页显示的留言列表提供内容概览;留言详情页展示完整内容和元信息;新增留言功能支持富文本内容创建;编辑功能允许作者修改已有内容;删除功能实现内容移除。每个功能点都进行了严格的权限控制,确保用户只能管理自己的内容,同时所有数据库操作都通过ORM进行,避免SQL注入风险
核心代码 (main/views.py):
@bp.route('/')
def index():
page = request.args.get('page', 1, type=int)
messages = Message.query.order_by(Message.created_at.desc()).paginate(
page=page, per_page=current_app.config['MESSAGES_PER_PAGE'])
return render_template('main/messages.html', messages=messages)
@bp.route('/create_message', methods=['GET', 'POST'])
@login_required
def create_message():
form = MessageForm()
if form.validate_on_submit():
message = Message(
title=form.title.data,
content=form.content.data,
author=current_user
)
db.session.add(message)
db.session.commit()
flash('留言已发布!')
return redirect(url_for('main.index'))
return render_template('main/create_message.html', form=form)
@bp.route('/message/<int:id>')
def message_detail(id):
message = Message.query.get_or_404(id)
return render_template('main/message_detail.html', message=message)
@bp.route('/edit_message/<int:id>', methods=['GET', 'POST'])
@login_required
def edit_message(id):
message = Message.query.get_or_404(id)
if message.author != current_user:
abort(403)
form = MessageForm()
if form.validate_on_submit():
message.title = form.title.data
message.content = form.content.data
db.session.commit()
flash('留言已更新!')
return redirect(url_for('main.message_detail', id=id))
elif request.method == 'GET':
form.title.data = message.title
form.content.data = message.content
return render_template('main/edit_message.html', form=form)
@bp.route('/delete_message/<int:id>', methods=['POST'])
@login_required
def delete_message(id):
message = Message.query.get_or_404(id)
if message.author != current_user:
abort(403)
db.session.delete(message)
db.session.commit()
flash('留言已删除!')
return redirect(url_for('main.index'))
五、页面设计
1、登陆模块
以下我分别用dbb、hahaha、dbbb三个号来讲述
登录页面 (login.html) 特点:
登录页面采用Bootstrap 5框架实现响应式布局,能够完美适配从手机到桌面电脑的各种设备屏幕尺寸。表单设计实现了双重验证机制,既包含前端JavaScript实时验证,又通过Flask-WTF进行服务器端校验,确保数据安全性。错误提示采用醒目的红色边框和文字说明,直观展示验证失败的具体原因,帮助用户快速定位问题。"记住我"功能通过设置持久化Cookie实现,为用户提供便捷的自动登录体验。页面底部设有明显的注册入口链接,引导未注册用户快速跳转到注册页面,形成完整的用户引导流程。
代码如下
{% extends "main/base.html" %}
{% block title %}登录{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h2 class="card-title text-center mb-4">登录</h2>
<form method="POST" action="{{ url_for('auth.login') }}">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.username.label(class="form-label") }}
{{ form.username(class="form-control") }}
{% if form.username.errors %}
<div class="invalid-feedback d-block">
{% for error in form.username.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control") }}
{% if form.password.errors %}
<div class="invalid-feedback d-block">
{% for error in form.password.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3 form-check">
{{ form.remember_me(class="form-check-input") }}
{{ form.remember_me.label(class="form-check-label") }}
</div>
<div class="d-grid">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
<div class="mt-3 text-center">
<p>新用户?<a href="{{ url_for('auth.register') }}">点击注册</a></p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
注册页面 (register.html) 特点:
注册页面实现了全面的表单验证体系,包括用户名唯一性检查(实时查询数据库)、标准邮箱格式验证(使用email-validator库)以及密码强度要求(最少8位字符)。密码确认字段通过WTForms的EqualTo验证器确保两次输入一致,防止输入错误。所有验证结果都会通过动态错误提示实时反馈,在输入框下方即时显示具体错误信息,大幅提升填写效率。页面保留了良好的用户引导设计,底部设有登录入口链接,方便已有账号的用户快速切换到登录界面,形成注册-登录的闭环体验。
代码如下:{% extends "main/base.html" %}
{% block title %}注册{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h2 class="card-title text-center mb-4">注册</h2>
<form method="POST" action="{{ url_for('auth.register') }}">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.username.label(class="form-label") }}
{{ form.username(class="form-control") }}
{% if form.username.errors %}
<div class="invalid-feedback d-block">
{% for error in form.username.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control") }}
{% if form.email.errors %}
<div class="invalid-feedback d-block">
{% for error in form.email.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control") }}
{% if form.password.errors %}
<div class="invalid-feedback d-block">
{% for error in form.password.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.password2.label(class="form-label") }}
{{ form.password2(class="form-control") }}
{% if form.password2.errors %}
<div class="invalid-feedback d-block">
{% for error in form.password2.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="d-grid">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
<div class="mt-3 text-center">
<p>已有账号?<a href="{{ url_for('auth.login') }}">点击登录</a></p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
2、留言板模块
基础模板 (base.html) 特点:
基础模板采用Bootstrap 5框架构建统一的UI风格,确保整个应用保持一致的视觉效果。响应式导航栏能自动适应不同设备屏幕尺寸,并根据用户登录状态动态显示对应的功能入口(如登录用户显示"发布留言"和"退出"按钮,未登录用户显示"登录"和"注册"按钮)。通过Flask的消息闪现系统,所有操作结果都会以醒目的提示框形式展示在页面顶部,增强用户交互体验。导航栏还实时显示当前登录用户名,让用户随时感知自己的登录状态。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}留言板{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
padding-top: 20px;
background-color: #f8f9fa;
}
.message-card {
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.pagination {
justify-content: center;
margin-top: 20px;
}
/* 确保导航栏项目正确对齐 */
.navbar-nav {
align-items: center;
}
</style>
</head>
<body>
<div class="container">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('main.index') }}">留言板</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.index') }}">首页</a>
</li>
{% if current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.create_message') }}">发布留言</a>
</li>
{% endif %}
</ul>
<ul class="navbar-nav">
{% if current_user.is_authenticated %}
<li class="nav-item">
<span class="navbar-text me-2">欢迎, {{ current_user.username }}</span>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}" id="logoutLink">退出</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.login') }}">登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.register') }}">注册</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 确保退出链接正常工作
document.getElementById('logoutLink')?.addEventListener('click', function() {
// 可以在这里添加确认对话框
return confirm('确定要退出吗?');
});
</script>
</body>
</html>
留言列表 (messages.html) 特点:
留言列表页面采用卡片式布局展示内容摘要,每张卡片包含留言标题、部分内容和关键元信息。分页控件位于页面底部,支持大量数据的分批加载和浏览。每条留言卡片都清晰标注作者用户名和发布时间,并采用条件渲染技术,仅对留言作者显示"编辑"和"删除"操作按钮。卡片标题链接到详情页,方便用户查看完整内容。整体布局简洁明了,信息层级分明。
代码如下:
{% extends "main/base.html" %}
{% block title %}留言板{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8 mx-auto">
<h1 class="mb-4">所有留言</h1>
{% for message in messages.items %}
<div class="card message-card mb-3">
<div class="card-body">
<h2 class="card-title">
<a href="{{ url_for('main.message_detail', id=message.id) }}" class="text-decoration-none">
{{ message.title }}
</a>
</h2>
<p class="card-text text-muted">
{{ message.content[:100] }}... <!-- 显示前100个字符 -->
</p>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
作者: {{ message.author.username }} |
发布于: {{ message.created_at.strftime('%Y-%m-%d %H:%M') }}
</small>
{% if current_user == message.author %}
<div>
<a href="{{ url_for('main.edit_message', id=message.id) }}" class="btn btn-sm btn-outline-primary">编辑</a>
</div>
{% endif %}
</div>
</div>
</div>
{% else %}
<div class="alert alert-info">暂无留言</div>
{% endfor %}
<!-- 分页导航 -->
<nav aria-label="Message navigation">
<ul class="pagination justify-content-center">
{% if messages.has_prev %}
<li class="page-item">
<a class="page-link" href="{{ url_for('main.index', page=messages.prev_num) }}">上一页</a>
</li>
{% endif %}
<li class="page-item disabled">
<span class="page-link">第 {{ messages.page }} 页 / 共 {{ messages.pages }} 页</span>
</li>
{% if messages.has_next %}
<li class="page-item">
<a class="page-link" href="{{ url_for('main.index', page=messages.next_num) }}">下一页</a>
</li>
{% endif %}
</ul>
</nav>
<div class="text-center mt-3">
<a href="{{ url_for('main.create_message') }}" class="btn btn-primary">发布新留言</a>
</div>
</div>
</div>
{% endblock %}
留言详情 (message_detail.html) 特点:
详情页完整展示留言的标题和内容,在显著位置标注作者信息和创建时间。如果留言被编辑过,还会额外显示最后更新时间,让读者了解内容的新鲜度。页面底部为留言作者提供了专属操作区,包含"编辑"和"删除"按钮,非作者用户则看不到这些功能入口。所有内容采用舒适的排版和合适的字体大小,确保最佳阅读体验。
代码如下:
{% extends "main/base.html" %}
{% block title %}{{ message.title }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card message-card">
<div class="card-body">
<h2 class="card-title">{{ message.title }}</h2>
<div class="card-text mb-4">
{{ message.content|safe }}
</div>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
作者: {{ message.author.username }} |
发布于: {{ message.created_at.strftime('%Y-%m-%d %H:%M') }} |
最后更新: {{ message.updated_at.strftime('%Y-%m-%d %H:%M') }}
</small>
{% if current_user == message.author %}
<div>
<a href="{{ url_for('main.edit_message', id=message.id) }}" class="btn btn-outline-primary">编辑</a>
<form action="{{ url_for('main.delete_message', id=message.id) }}" method="post" class="d-inline">
<button type="submit" class="btn btn-outline-danger" οnclick="return confirm('确定删除吗?')">删除</button>
</form>
</div>
{% endif %}
</div>
</div>
</div>
<div class="mt-3">
<a href="{{ url_for('main.index') }}" class="btn btn-secondary">返回列表</a>
</div>
</div>
</div>
{% endblock %}
创建/编辑页面 (create_message.html/edit_message.html) 特点:
这两个页面共享相似的表单结构,都实现了严格的表单验证机制,包括必填项检查和内容长度限制。富文本编辑区域支持基本的格式设置,提升内容输入体验。在编辑模式下,表单会自动载入原有数据,方便用户修改。所有破坏性操作(如删除)都要求二次确认,通过JavaScript弹窗提示用户确认操作,有效防止误操作导致的数据丢失。页面布局保持简洁,突出核心的表单区域。
create_message.html:
{% extends "main/base.html" %}
{% block title %}发布留言{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8 mx-auto">
<h1 class="mb-4">发布留言</h1>
<form method="POST" action="{{ url_for('main.create_message') }}">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.title.label(class="form-label") }}
{{ form.title(class="form-control") }}
{% if form.title.errors %}
<div class="invalid-feedback d-block">
{% for error in form.title.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.content.label(class="form-label") }}
{{ form.content(class="form-control", rows=8) }}
{% if form.content.errors %}
<div class="invalid-feedback d-block">
{% for error in form.content.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="d-grid">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
</div>
</div>
{% endblock %}
edit_message.html:
{% extends "main/base.html" %}
{% block title %}编辑留言{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8 mx-auto">
<h1 class="mb-4">编辑留言</h1>
<form method="POST" action="{{ url_for('main.edit_message', id=message.id) }}">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.title.label(class="form-label") }}
{{ form.title(class="form-control") }}
{% if form.title.errors %}
<div class="invalid-feedback d-block">
{% for error in form.title.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.content.label(class="form-label") }}
{{ form.content(class="form-control", rows=8) }}
{% if form.content.errors %}
<div class="invalid-feedback d-block">
{% for error in form.content.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<a href="{{ url_for('main.message_detail', id=message.id) }}" class="btn btn-secondary me-md-2">取消</a>
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
</div>
</div>
{% endblock %}
第一次进入页面
使用dbbb这个号第三次进入页面就有之前两个的留言
用第三个dbbb号进入并发布新留言
编辑留言
成功删除留言