Flask + Vue3 前后端分离管理系统
项目概述
本项目是一个基于 Flask 后端和 Vue3 前端的前后端分离管理系统。项目实现了用户管理、角色管理、菜单管理、权限控制等完整的后台管理功能。
技术栈
后端技术栈:
- Flask 3.0.0 - Python Web框架
- Flask-SQLAlchemy 3.1.1 - ORM数据库操作
- Flask-JWT-Extended 4.6.0 - JWT身份认证
- Flask-CORS 4.0.0 - 跨域资源共享
- PyMySQL 1.1.0 - MySQL数据库驱动
- passlib[bcrypt] 1.7.4 - 密码加密(兼容Django)
前端技术栈:
- Vue 3 - 渐进式JavaScript框架
- TypeScript - 类型安全的JavaScript
- Element Plus - Vue 3 UI组件库
- Pinia - Vue 3状态管理
- Vite - 现代化构建工具
- TailwindCSS - 原子化CSS框架
项目结构
Flask_Pure/
├── Flask-Backend/ # Flask后端项目
│ ├── app/ # 应用核心代码
│ │ ├── init.py # 应用工厂
│ │ ├── models.py # 数据模型
│ │ ├── auth.py # 认证模块
│ │ ├── user.py # 用户管理
│ │ ├── role.py # 角色管理
│ │ ├── menu.py # 菜单管理
│ │ ├── permission.py # 权限管理
│ │ └── utils.py # 工具函数
│ ├── config.py # 配置文件
│ ├── requirements.txt # Python依赖
│ └── run.py # 启动脚本
├── Frontend/ # Vue3前端项目
│ ├── src/ # 源代码
│ │ ├── api/ # API接口
│ │ ├── components/ # 组件
│ │ ├── views/ # 页面视图
│ │ ├── store/ # 状态管理
│ │ └── utils/ # 工具函数
│ └── package.json # 前端依赖
核心功能实现
1. 用户认证系统
登录功能

Flask后端实现了完整的JWT认证系统:
python
@auth_bp.route('/auth/login', methods=['POST'])
def login():
"""用户登录"""
try:
data = request.get_json()
username = data.get('username')
password = data.get('password')
# 查找用户
user = User.query.filter_by(username=username).first()
if not user or not user.check_password(password):
return jsonify({'code': 401, 'message': '用户名或密码错误'}), 401
# 创建JWT令牌
access_token = create_access_token(identity=str(user.id))
refresh_token = create_refresh_token(identity=str(user.id))
return jsonify({
'code': 0,
'data': {'accessToken': access_token}
})
except Exception as e:
return jsonify({'code': 500, 'message': str(e)}), 500
密码兼容性处理
为了兼容Django的密码哈希,我们实现了特殊的密码处理逻辑:
python
def django_password_hash(password, salt=None):
"""使用Django的pbkdf2_sha256方法生成密码哈希"""
if salt is None:
salt = secrets.token_bytes(12)
hash_obj = pbkdf2_sha256.hash(password, salt=salt)
return hash_obj
def check_password_compatible(password, hashed_password):
"""兼容性密码验证,支持Django和Flask格式"""
if is_django_password(hashed_password):
return django_password_verify(password, hashed_password)
else:
return check_password_hash(hashed_password, password)
2. 用户管理系统

用户列表接口
python
@user_bp.route('/user/list', methods=['GET'])
@jwt_required()
def get_user_list():
"""获取用户列表 - 完全匹配Django格式"""
try:
# 分页参数
page = request.args.get('page', 1, type=int)
page_size = request.args.get('page_size', 10, type=int)
# 构建查询
query = User.query.order_by(User.date_joined.desc())
# 分页
pagination = query.paginate(page=page, per_page=page_size, error_out=False)
# 构建用户数据
users = []
for user in pagination.items:
user_data = {
'id': user.id,
'username': user.username,
'realName': user.realName or '',
'email': user.email or '',
'is_active': user.is_active,
'groups': [{'id': group.id, 'name': group.name} for group in user.groups],
'date_joined': user.date_joined.isoformat() if user.date_joined else None,
'last_login': user.last_login.isoformat() if user.last_login else None
}
users.append(user_data)
return jsonify({
'code': 0,
'data': {
'items': users,
'total': pagination.total,
'page': page,
'pageSize': page_size
}
}), 200
except Exception as e:
return jsonify({'code': 500, 'message': str(e)}), 500
3. 角色管理系统

角色权限管理
python
@role_bp.route('/role/<int:role_id>/permissions', methods=['GET'])
@jwt_required()
def get_role_permissions(role_id):
"""获取角色权限"""
try:
group = Group.query.get(role_id)
if not group:
return jsonify({'code': 404, 'message': '角色不存在'}), 404
# 获取所有权限
all_permissions = Permission.query.all()
role_permissions = group.permissions
return jsonify({
'code': 0,
'data': {
'all_permissions': [
{
'id': perm.id,
'name': perm.name,
'codename': perm.codename,
'content_type': f"{perm.content_type.app_label}.{perm.content_type.model}" if perm.content_type else f"content_type_{perm.content_type_id}"
} for perm in all_permissions
],
'role_permissions': [
{
'id': perm.id,
'name': perm.name,
'codename': perm.codename,
'content_type': f"{perm.content_type.app_label}.{perm.content_type.model}" if perm.content_type else f"content_type_{perm.content_type_id}"
} for perm in role_permissions
]
}
}), 200
except Exception as e:
return jsonify({'code': 500, 'message': str(e)}), 500
4. 菜单管理系统

菜单树形结构
python
@menu_bp.route('/menu/list', methods=['GET'])
@jwt_required()
def get_menu_list():
"""获取菜单列表(扁平化,用于管理界面)"""
try:
# 获取所有菜单,按order排序
all_menus = Menu.query.order_by(Menu.order).all()
def build_flat_menu_list(menus):
"""构建扁平化的菜单列表,包含父子关系"""
flat_list = []
for menu in menus:
menu_data = {
'id': menu.id,
'name': menu.name,
'path': menu.path,
'component': menu.component,
'redirect': menu.redirect,
'meta': menu.meta or {},
'parent': menu.parent_id,
'parent_name': menu.parent.name if menu.parent else None,
'order': menu.order,
'groups': [{'id': group.id, 'name': group.name} for group in menu.groups],
'children': []
}
# 递归处理子菜单
children = Menu.query.filter_by(parent_id=menu.id).order_by(Menu.order).all()
for child in children:
child_data = build_flat_menu_list([child])[0]
menu_data['children'].append(child_data)
flat_list.append(menu_data)
return flat_list
# 构建扁平化菜单列表
menu_list = build_flat_menu_list(all_menus)
return jsonify({
'code': 0,
'data': menu_list
}), 200
except Exception as e:
return jsonify({'code': 500, 'message': str(e)}), 500
5. 个人信息管理

用户信息更新
python
@user_bp.route('/user/update-info', methods=['POST'])
@jwt_required()
def update_user_info():
"""更新用户信息"""
try:
user_id = get_jwt_identity()
user = User.query.get(int(user_id))
if not user:
return jsonify({'code': 404, 'message': '用户不存在'}), 404
data = request.get_json()
# 更新允许的字段
if 'realName' in data:
user.realName = data['realName']
if 'phone' in data:
user.phone = data['phone']
if 'email' in data:
user.email = data['email']
if 'desc' in data:
user.desc = data['desc']
if 'gender' in data:
user.gender = data['gender']
db.session.commit()
return jsonify({
'code': 0,
'message': '个人信息更新成功'
}), 200
except Exception as e:
db.session.rollback()
return jsonify({'code': 500, 'message': str(e)}), 500
6. 账户管理

密码修改功能
python
@user_bp.route('/user/change-password', methods=['POST'])
@jwt_required()
def change_password():
"""修改密码"""
try:
user_id = get_jwt_identity()
user = User.query.get(int(user_id))
if not user:
return jsonify({'code': 404, 'message': '用户不存在'}), 404
data = request.get_json()
old_password = data.get('oldPassword')
new_password = data.get('newPassword')
if not old_password or not new_password:
return jsonify({'code': 400, 'message': '旧密码和新密码不能为空'}), 400
if not user.check_password(old_password):
return jsonify({'code': 400, 'message': '旧密码错误'}), 400
user.set_password(new_password)
db.session.commit()
return jsonify({
'code': 0,
'message': '密码修改成功'
}), 200
except Exception as e:
db.session.rollback()
return jsonify({'code': 500, 'message': str(e)}), 500
数据库设计
核心数据模型
python
class User(db.Model, UserMixin):
"""用户模型"""
__tablename__ = 'app_user'
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
username = db.Column(db.String(150), unique=True, nullable=False)
password = db.Column(db.String(128), nullable=False)
email = db.Column(db.String(254), nullable=True)
realName = db.Column(db.String(150), nullable=True)
phone = db.Column(db.String(150), nullable=True)
avatar = db.Column(db.String(255), nullable=True)
gender = db.Column(db.String(1), nullable=True)
desc = db.Column(db.Text, nullable=True)
is_active = db.Column(db.Boolean, default=True)
is_staff = db.Column(db.Boolean, default=False)
is_superuser = db.Column(db.Boolean, default=False)
date_joined = db.Column(db.DateTime, default=datetime.utcnow)
last_login = db.Column(db.DateTime, nullable=True)
# 关系
groups = db.relationship('Group', secondary='app_user_groups', backref='users')
user_permissions = db.relationship('Permission', secondary='app_user_user_permissions', backref='users')
class Group(db.Model):
"""用户组模型"""
__tablename__ = 'auth_group'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(150), unique=True, nullable=False)
# 关系
permissions = db.relationship('Permission', secondary='auth_group_permissions', backref='groups')
class Permission(db.Model):
"""权限模型"""
__tablename__ = 'auth_permission'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(255), nullable=False)
content_type_id = db.Column(db.Integer, db.ForeignKey('django_content_type.id'), nullable=False)
codename = db.Column(db.String(100), nullable=False)
class Menu(db.Model):
"""菜单模型"""
__tablename__ = 'app_menu'
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
path = db.Column(db.String(255), nullable=False)
component = db.Column(db.String(255), nullable=True)
redirect = db.Column(db.String(255), nullable=True)
meta = db.Column(db.JSON, default=dict)
parent_id = db.Column(db.BigInteger, db.ForeignKey('app_menu.id'), nullable=True)
order = db.Column(db.Integer, default=0)
# 关系
parent = db.relationship('Menu', remote_side=[id], backref='children')
groups = db.relationship('Group', secondary='app_menu_groups', backref='menus')
前端实现
1. 状态管理
使用Pinia进行状态管理:
typescript
// store/modules/user.ts
export const useUserStore = defineStore("user", {
state: (): UserState => ({
username: storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? "",
nickname: storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "",
avatar: storageLocal().getItem<DataInfo<number>>(userKey)?.avatar ?? "",
roles: storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [],
permissions: storageLocal().getItem<DataInfo<number>>(userKey)?.permissions ?? []
}),
actions: {
/** 登录 */
async loginByUsername(data: LoginForm) {
return new Promise<LoginResult>((resolve, reject) => {
getLogin(data)
.then(data => {
if (data?.code === 0) {
setToken(data.data);
resolve(data);
}
})
.catch(error => {
reject(error);
});
});
}
}
});
2. HTTP请求拦截
typescript
// utils/http/index.ts
private httpInterceptorsRequest(): void {
PureHttp.axiosInstance.interceptors.request.use(
async (config: PureHttpRequestConfig): Promise<any> => {
const whiteList = ["/api/auth/refresh", "/api/auth/login", "/api/auth/register"];
return whiteList.some(url => config.url.endsWith(url))
? config
: new Promise(resolve => {
const data = getToken();
if (data) {
const now = new Date().getTime();
const expired = parseInt(data.expires) - now <= 0;
if (expired) {
// token过期刷新逻辑
useUserStoreHook().handRefreshToken().then(res => {
const token = res.data;
config.headers["Authorization"] = formatToken(token);
resolve(config);
});
} else {
config.headers["Authorization"] = formatToken(data.accessToken);
resolve(config);
}
} else {
resolve(config);
}
});
}
);
}
3. 路由权限控制
typescript
// router/index.ts
router.beforeEach(async (to, _from, next) => {
const userStore = useUserStoreHook();
const token = getToken();
if (token) {
if (to.path === "/login") {
next({ path: "/" });
} else {
if (userStore.roles.length === 0) {
try {
await userStore.getUserInfo();
next({ ...to, replace: true });
} catch {
userStore.resetState();
next("/login");
}
} else {
next();
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
next("/login");
}
}
});
部署配置
1. 后端部署
环境要求
- Python 3.8+
- MySQL 5.7+
- Redis (可选)
安装依赖
bash
cd Flask-Backend
pip install -r requirements.txt
配置数据库
python
# config.py
class Config:
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:password@localhost:3306/database_name?charset=utf8mb4'
JWT_SECRET_KEY = 'your-secret-key'
SECRET_KEY = 'your-flask-secret-key'
启动服务
bash
python run.py
2. 前端部署
环境要求
- Node.js 16+
- pnpm (推荐) 或 npm
安装依赖
bash
cd Frontend
pnpm install
开发环境启动
bash
pnpm dev
生产环境构建
bash
pnpm build
系统截图展示
首页

系统首页展示了整体的数据概览和快速操作入口,包括用户统计、系统状态等信息。
系统设置

系统设置页面提供了系统级别的配置选项,包括基本设置、安全配置等。
技术亮点
1. 完全兼容Django后端
- 数据库表结构完全一致
- API接口格式完全匹配
- 密码哈希算法兼容
- 权限系统完全对应
2. 现代化技术栈
- Flask 3.0最新版本
- Vue 3 Composition API
- TypeScript类型安全
- Element Plus组件库
3. 安全性保障
- JWT身份认证
- 密码加密存储
- 跨域安全配置
- 权限细粒度控制
4. 开发体验优化
- 热重载开发
- TypeScript类型提示
- 统一的错误处理
- 完整的日志记录
高级技术实现
1. 权限管理系统
细粒度权限控制
系统实现了基于Django权限模型的细粒度权限控制:
python
# Flask-Backend/app/permission.py
@permission_bp.route('/permission/list', methods=['GET'])
@jwt_required()
def get_permission_list():
"""获取权限列表"""
try:
user_id = get_jwt_identity()
current_user = User.query.get(int(user_id))
if not current_user:
return jsonify({'code': 404, 'message': '用户不存在'}), 404
# 获取所有权限
permissions = Permission.query.all()
permissions_data = []
for perm in permissions:
perm_data = {
'id': perm.id,
'name': perm.name,
'codename': perm.codename,
'content_type': perm.content_type_id
}
permissions_data.append(perm_data)
return jsonify({
'code': 0,
'data': permissions_data
}), 200
except Exception as e:
return jsonify({'code': 500, 'message': str(e)}), 500
权限验证装饰器
python
def permission_required(permission_codename):
"""权限验证装饰器"""
def decorator(f):
@wraps(f)
@jwt_required()
def decorated_function(*args, **kwargs):
user_id = get_jwt_identity()
user = User.query.get(int(user_id))
if not user or not user.has_perm(permission_codename):
return jsonify({'code': 403, 'message': '权限不足'}), 403
return f(*args, **kwargs)
return decorated_function
return decorator
2. 通知系统
实时通知功能
系统实现了完整的通知管理功能,支持多种通知类型:
python
# Flask-Backend/app/notification.py
@notification_bp.route('/notification/list', methods=['GET'])
@jwt_required()
def get_notification_list():
"""获取通知列表"""
try:
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': '用户不存在'}), 404
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 20, type=int)
notification_type = request.args.get('type', '')
# 获取用户收到的通知
query = Notification.query.join(Notification.recipients).filter(
Notification.recipients.any(id=user_id)
)
if notification_type:
query = query.filter(Notification.notification_type == notification_type)
pagination = query.order_by(Notification.created_at.desc()).paginate(
page=page,
per_page=per_page,
error_out=False
)
notifications = []
for notification in pagination.items:
# 获取通知状态
status = NotificationStatus.query.filter_by(
notification_id=notification.id,
user_id=user_id
).first()
notifications.append({
'id': notification.id,
'title': notification.title,
'message': notification.message,
'notification_type': notification.notification_type,
'link': notification.link,
'created_at': notification.created_at.isoformat(),
'time_ago': notification.time_ago,
'is_read': status.is_read if status else False,
'sender': {
'id': notification.sender.id,
'username': notification.sender.username,
'realName': notification.sender.realName
} if notification.sender else None
})
return jsonify({
'count': pagination.total,
'next': pagination.next_num if pagination.has_next else None,
'previous': pagination.prev_num if pagination.has_prev else None,
'results': notifications
}), 200
except Exception as e:
return jsonify({'error': str(e)}), 500
3. 文件上传系统
头像上传功能
python
@user_bp.route('/user/upload-avatar', methods=['POST'])
@jwt_required()
def upload_avatar():
"""上传用户头像"""
try:
user_id = get_jwt_identity()
user = User.query.get(int(user_id))
if not user:
return jsonify({'code': 404, 'message': '用户不存在'}), 404
if 'file' not in request.files:
return jsonify({'code': 400, 'message': '没有选择文件'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'code': 400, 'message': '没有选择文件'}), 400
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
# 生成唯一文件名
unique_filename = f"{user.id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{filename}"
file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], 'avatars', unique_filename)
# 确保目录存在
os.makedirs(os.path.dirname(file_path), exist_ok=True)
# 保存文件
file.save(file_path)
# 更新用户头像路径
user.avatar = f"avatars/{unique_filename}"
db.session.commit()
return jsonify({
'code': 0,
'data': {
'avatar': user.avatar,
'url': f"/media/{user.avatar}"
},
'message': '头像上传成功'
}), 200
else:
return jsonify({'code': 400, 'message': '不支持的文件类型'}), 400
except Exception as e:
db.session.rollback()
return jsonify({'code': 500, 'message': str(e)}), 500
4. 数据库连接池优化
连接池配置
python
# Flask-Backend/config.py
class Config:
"""基础配置类"""
# 数据库配置
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@127.0.0.1:3306/Django_Pure?charset=utf8mb4'
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ENGINE_OPTIONS = {
'pool_size': 10, # 连接池大小
'pool_recycle': 3600, # 连接回收时间(秒)
'pool_pre_ping': True # 连接前预检查
}
# JWT配置
JWT_SECRET_KEY = 'jwt-secret-key-1723&ea+jkt1c(rf2ez6ur+m)%olhfu@v(i-@95&xu#$o^rx'
JWT_ACCESS_TOKEN_EXPIRES = timedelta(minutes=30)
JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=7)
JWT_TOKEN_LOCATION = ['headers']
JWT_HEADER_NAME = 'Authorization'
JWT_HEADER_TYPE = 'Bearer'
# 文件上传配置
UPLOAD_FOLDER = 'media'
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
# CORS配置
CORS_ORIGINS = [
"http://127.0.0.1:5777",
"http://localhost:5777"
]
CORS_SUPPORTS_CREDENTIALS = True
# 分页配置
ITEMS_PER_PAGE = 10
5. 错误处理机制
统一错误处理
python
# Flask-Backend/app/errors.py
from flask import jsonify
from flask_jwt_extended import JWTManager
def register_error_handlers(app):
"""注册错误处理器"""
@app.errorhandler(400)
def bad_request(error):
return jsonify({
'code': 400,
'message': '请求参数错误'
}), 400
@app.errorhandler(401)
def unauthorized(error):
return jsonify({
'code': 401,
'message': '未授权访问'
}), 401
@app.errorhandler(403)
def forbidden(error):
return jsonify({
'code': 403,
'message': '权限不足'
}), 403
@app.errorhandler(404)
def not_found(error):
return jsonify({
'code': 404,
'message': '资源不存在'
}), 404
@app.errorhandler(500)
def internal_error(error):
return jsonify({
'code': 500,
'message': '服务器内部错误'
}), 500
# JWT错误处理
@JWTManager.expired_token_loader
def expired_token_callback(jwt_header, jwt_payload):
return jsonify({
'code': 401,
'message': 'Token已过期'
}), 401
@JWTManager.invalid_token_loader
def invalid_token_callback(error):
return jsonify({
'code': 401,
'message': '无效的Token'
}), 401
@JWTManager.unauthorized_loader
def missing_token_callback(error):
return jsonify({
'code': 401,
'message': '缺少Authorization头'
}), 401
6. 性能优化策略
数据库查询优化
python
# 使用预加载减少N+1查询问题
def get_user_with_relations(user_id):
"""获取用户及其关联数据"""
return User.query.options(
db.joinedload(User.groups),
db.joinedload(User.user_permissions)
).get(user_id)
# 使用索引优化查询
class User(db.Model):
__tablename__ = 'app_user'
# 为常用查询字段添加索引
username = db.Column(db.String(150), unique=True, nullable=False, index=True)
email = db.Column(db.String(254), nullable=True, index=True)
is_active = db.Column(db.Boolean, default=True, index=True)
缓存策略
python
from flask_caching import Cache
cache = Cache()
# 缓存用户信息
@cache.memoize(timeout=300) # 5分钟缓存
def get_user_info(user_id):
"""获取用户信息(带缓存)"""
return User.query.get(user_id)
# 缓存菜单数据
@cache.memoize(timeout=600) # 10分钟缓存
def get_menu_tree():
"""获取菜单树(带缓存)"""
return build_menu_tree()
7. 安全防护措施
密码安全
python
# Flask-Backend/app/utils.py
def django_password_hash(password, salt=None):
"""使用Django的pbkdf2_sha256方法生成密码哈希"""
if salt is None:
# 生成随机的bytes类型的salt
salt = secrets.token_bytes(12)
# 使用pbkdf2_sha256算法
hash_obj = pbkdf2_sha256.hash(password, salt=salt)
return hash_obj
def check_password_compatible(password, hashed_password):
"""兼容性密码验证,支持Django和Flask格式"""
print(f"检查密码哈希格式: {hashed_password[:50]}...")
print(f"是否为Django格式: {is_django_password(hashed_password)}")
if is_django_password(hashed_password):
print("使用Django密码验证")
return django_password_verify(password, hashed_password)
else:
print("使用Flask密码验证")
return check_password_hash(hashed_password, password)
输入验证
python
from marshmallow import Schema, fields, validate
class UserCreateSchema(Schema):
"""用户创建验证模式"""
username = fields.Str(required=True, validate=[
validate.Length(min=3, max=150),
validate.Regexp(r'^[a-zA-Z0-9_]+$', error='用户名只能包含字母、数字和下划线')
])
email = fields.Email(required=True)
password = fields.Str(required=True, validate=validate.Length(min=6))
realName = fields.Str(validate=validate.Length(max=150))
phone = fields.Str(validate=validate.Regexp(r'^1[3-9]\d{9}$', error='手机号格式不正确'))
def validate_user_data(data):
"""验证用户数据"""
schema = UserCreateSchema()
try:
return schema.load(data)
except ValidationError as err:
return None, err.messages
8. 日志系统
结构化日志
python
import logging
from logging.handlers import RotatingFileHandler
import json
class JSONFormatter(logging.Formatter):
"""JSON格式化器"""
def format(self, record):
log_entry = {
'timestamp': self.formatTime(record),
'level': record.levelname,
'logger': record.name,
'message': record.getMessage(),
'module': record.module,
'function': record.funcName,
'line': record.lineno
}
if record.exc_info:
log_entry['exception'] = self.formatException(record.exc_info)
return json.dumps(log_entry, ensure_ascii=False)
def setup_logging(app):
"""设置日志"""
if not app.debug:
# 文件日志
file_handler = RotatingFileHandler(
'logs/app.log',
maxBytes=10240000, # 10MB
backupCount=10
)
file_handler.setFormatter(JSONFormatter())
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
# 控制台日志
console_handler = logging.StreamHandler()
console_handler.setFormatter(JSONFormatter())
console_handler.setLevel(logging.INFO)
app.logger.addHandler(console_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('应用启动')
9. API文档生成
Swagger集成
python
from flask_restx import Api, Resource, fields
api = Api(app, doc='/docs/', title='Flask Admin API', version='1.0')
# 定义数据模型
user_model = api.model('User', {
'id': fields.Integer(description='用户ID'),
'username': fields.String(description='用户名'),
'email': fields.String(description='邮箱'),
'realName': fields.String(description='真实姓名'),
'is_active': fields.Boolean(description='是否激活')
})
# 定义API端点
@api.route('/users')
class UserList(Resource):
@api.doc('get_users')
@api.marshal_list_with(user_model)
def get(self):
"""获取用户列表"""
users = User.query.all()
return users
10. 测试策略
单元测试
python
import unittest
from app import create_app, db
from app.models import User
class UserModelTestCase(unittest.TestCase):
def setUp(self):
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
def test_user_creation(self):
"""测试用户创建"""
user = User(
username='testuser',
email='test@example.com',
realName='测试用户'
)
user.set_password('password123')
db.session.add(user)
db.session.commit()
self.assertTrue(user.check_password('password123'))
self.assertFalse(user.check_password('wrongpassword'))
def test_user_authentication(self):
"""测试用户认证"""
user = User.query.filter_by(username='testuser').first()
self.assertIsNotNone(user)
self.assertTrue(user.check_password('password123'))
集成测试
python
class AuthAPITestCase(unittest.TestCase):
def setUp(self):
self.app = create_app('testing')
self.client = self.app.test_client()
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
# 创建测试用户
user = User(
username='testuser',
email='test@example.com',
realName='测试用户'
)
user.set_password('password123')
db.session.add(user)
db.session.commit()
def test_login_success(self):
"""测试登录成功"""
response = self.client.post('/api/auth/login', json={
'username': 'testuser',
'password': 'password123'
})
self.assertEqual(response.status_code, 200)
data = response.get_json()
self.assertEqual(data['code'], 0)
self.assertIn('accessToken', data['data'])
def test_login_failure(self):
"""测试登录失败"""
response = self.client.post('/api/auth/login', json={
'username': 'testuser',
'password': 'wrongpassword'
})
self.assertEqual(response.status_code, 401)
data = response.get_json()
self.assertEqual(data['code'], 401)
11. 监控和运维
健康检查
python
@api.route('/health')
class HealthCheck(Resource):
def get(self):
"""健康检查"""
try:
# 检查数据库连接
db.session.execute('SELECT 1')
db_status = 'healthy'
except Exception as e:
db_status = f'unhealthy: {str(e)}'
return {
'status': 'healthy' if db_status == 'healthy' else 'unhealthy',
'database': db_status,
'timestamp': datetime.utcnow().isoformat(),
'version': '1.0.0'
}
性能监控
python
from flask import g
import time
@app.before_request
def before_request():
g.start_time = time.time()
@app.after_request
def after_request(response):
if hasattr(g, 'start_time'):
duration = time.time() - g.start_time
response.headers['X-Response-Time'] = f'{duration:.3f}s'
# 记录慢请求
if duration > 1.0: # 超过1秒的请求
app.logger.warning(f'Slow request: {request.path} took {duration:.3f}s')
return response
12. 部署最佳实践
Docker化部署
dockerfile
# Dockerfile
FROM python:3.9-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
default-libmysqlclient-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 创建非root用户
RUN useradd --create-home --shell /bin/bash app \
&& chown -R app:app /app
USER app
# 暴露端口
EXPOSE 5000
# 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "run:app"]
Docker Compose配置
yaml
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "5000:5000"
environment:
- FLASK_ENV=production
- DATABASE_URL=mysql+pymysql://root:password@db:3306/flask_admin
depends_on:
- db
- redis
volumes:
- ./media:/app/media
- ./logs:/app/logs
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: flask_admin
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- web
volumes:
mysql_data:
13. 性能调优建议
数据库优化
sql
-- 创建索引
CREATE INDEX idx_user_username ON app_user(username);
CREATE INDEX idx_user_email ON app_user(email);
CREATE INDEX idx_user_is_active ON app_user(is_active);
CREATE INDEX idx_notification_created_at ON app_notification(created_at);
CREATE INDEX idx_notification_recipients ON app_notification_recipients(user_id);
-- 查询优化
EXPLAIN SELECT * FROM app_user WHERE username = 'admin';
EXPLAIN SELECT * FROM app_notification n
JOIN app_notification_recipients nr ON n.id = nr.notification_id
WHERE nr.user_id = 1 ORDER BY n.created_at DESC;
应用层优化
python
# 使用连接池
from sqlalchemy.pool import QueuePool
engine = create_engine(
DATABASE_URL,
poolclass=QueuePool,
pool_size=20,
max_overflow=30,
pool_pre_ping=True,
pool_recycle=3600
)
# 使用缓存
from flask_caching import Cache
cache = Cache(app, config={
'CACHE_TYPE': 'redis',
'CACHE_REDIS_URL': 'redis://localhost:6379/0',
'CACHE_DEFAULT_TIMEOUT': 300
})
# 异步任务处理
from celery import Celery
celery = Celery('flask_admin', broker='redis://localhost:6379/1')
@celery.task
def send_notification_async(notification_id):
"""异步发送通知"""
# 发送通知逻辑
pass
技术亮点
1. 现代化技术栈
- Flask 3.0最新版本
- Vue 3 Composition API
- TypeScript类型安全
- Element Plus组件库
2. 安全性保障
- JWT身份认证
- 密码加密存储
- 跨域安全配置
- 权限细粒度控制
3. 开发体验优化
- 热重载开发
- TypeScript类型提示
- 统一的错误处理
- 完整的日志记录