【Flask + Vue3 前后端分离管理系统】
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认证系统:
@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的密码哈希,我们实现了特殊的密码处理逻辑:
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_objdef 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. 用户管理系统
用户列表接口
@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}}), 200except Exception as e:return jsonify({'code': 500, 'message': str(e)}), 500
3. 角色管理系统
角色权限管理
@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.permissionsreturn 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]}}), 200except Exception as e:return jsonify({'code': 500, 'message': str(e)}), 500
4. 菜单管理系统
菜单树形结构
@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}), 200except Exception as e:return jsonify({'code': 500, 'message': str(e)}), 500
5. 个人信息管理
用户信息更新
@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': '用户不存在'}), 404data = 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': '个人信息更新成功'}), 200except Exception as e:db.session.rollback()return jsonify({'code': 500, 'message': str(e)}), 500
6. 账户管理
密码修改功能
@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': '用户不存在'}), 404data = 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': '旧密码和新密码不能为空'}), 400if not user.check_password(old_password):return jsonify({'code': 400, 'message': '旧密码错误'}), 400user.set_password(new_password)db.session.commit()return jsonify({'code': 0,'message': '密码修改成功'}), 200except Exception as e:db.session.rollback()return jsonify({'code': 500, 'message': str(e)}), 500
数据库设计
核心数据模型
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进行状态管理:
// 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请求拦截
// 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. 路由权限控制
// 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 (可选)
安装依赖
cd Flask-Backend
pip install -r requirements.txt
配置数据库
# 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'
启动服务
python run.py
2. 前端部署
环境要求
- Node.js 16+
- pnpm (推荐) 或 npm
安装依赖
cd Frontend
pnpm install
开发环境启动
pnpm dev
生产环境构建
pnpm build
系统截图展示
首页
系统首页展示了整体的数据概览和快速操作入口,包括用户统计、系统状态等信息。
系统设置
系统设置页面提供了系统级别的配置选项,包括基本设置、安全配置等。
技术亮点
1. 完全兼容Django后端
- 数据库表结构完全一致
- API接口格式完全匹配
- 密码哈希算法兼容
- 权限系统完全对应
2. 现代化技术栈
- Flask 3.0最新版本
- Vue 3 Composition API
- TypeScript类型安全
- Element Plus组件库
3. 安全性保障
- JWT身份认证
- 密码加密存储
- 跨域安全配置
- 权限细粒度控制
4. 开发体验优化
- 热重载开发
- TypeScript类型提示
- 统一的错误处理
- 完整的日志记录
高级技术实现
1. 权限管理系统
细粒度权限控制
系统实现了基于Django权限模型的细粒度权限控制:
# 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}), 200except Exception as e:return jsonify({'code': 500, 'message': str(e)}), 500
权限验证装饰器
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': '权限不足'}), 403return f(*args, **kwargs)return decorated_functionreturn decorator
2. 通知系统
实时通知功能
系统实现了完整的通知管理功能,支持多种通知类型:
# 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': '用户不存在'}), 404page = 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}), 200except Exception as e:return jsonify({'error': str(e)}), 500
3. 文件上传系统
头像上传功能
@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': '用户不存在'}), 404if 'file' not in request.files:return jsonify({'code': 400, 'message': '没有选择文件'}), 400file = request.files['file']if file.filename == '':return jsonify({'code': 400, 'message': '没有选择文件'}), 400if 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': '头像上传成功'}), 200else:return jsonify({'code': 400, 'message': '不支持的文件类型'}), 400except Exception as e:db.session.rollback()return jsonify({'code': 500, 'message': str(e)}), 500
4. 数据库连接池优化
连接池配置
# 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 = FalseSQLALCHEMY_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 # 16MBALLOWED_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. 错误处理机制
统一错误处理
# Flask-Backend/app/errors.py
from flask import jsonify
from flask_jwt_extended import JWTManagerdef 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_loaderdef expired_token_callback(jwt_header, jwt_payload):return jsonify({'code': 401,'message': 'Token已过期'}), 401@JWTManager.invalid_token_loaderdef invalid_token_callback(error):return jsonify({'code': 401,'message': '无效的Token'}), 401@JWTManager.unauthorized_loaderdef missing_token_callback(error):return jsonify({'code': 401,'message': '缺少Authorization头'}), 401
6. 性能优化策略
数据库查询优化
# 使用预加载减少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)
缓存策略
from flask_caching import Cachecache = 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. 安全防护措施
密码安全
# Flask-Backend/app/utils.py
def django_password_hash(password, salt=None):"""使用Django的pbkdf2_sha256方法生成密码哈希"""if salt is None:# 生成随机的bytes类型的saltsalt = secrets.token_bytes(12)# 使用pbkdf2_sha256算法hash_obj = pbkdf2_sha256.hash(password, salt=salt)return hash_objdef 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)
输入验证
from marshmallow import Schema, fields, validateclass 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. 日志系统
结构化日志
import logging
from logging.handlers import RotatingFileHandler
import jsonclass 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, # 10MBbackupCount=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集成
from flask_restx import Api, Resource, fieldsapi = 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. 测试策略
单元测试
import unittest
from app import create_app, db
from app.models import Userclass 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'))
集成测试
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. 监控和运维
健康检查
@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'}
性能监控
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_timeresponse.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
FROM python:3.9-slimWORKDIR /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配置
# 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_admindepends_on:- db- redisvolumes:- ./media:/app/media- ./logs:/app/logsdb:image: mysql:8.0environment:MYSQL_ROOT_PASSWORD: passwordMYSQL_DATABASE: flask_adminvolumes:- mysql_data:/var/lib/mysqlports:- "3306:3306"redis:image: redis:7-alpineports:- "6379:6379"nginx:image: nginx:alpineports:- "80:80"- "443:443"volumes:- ./nginx.conf:/etc/nginx/nginx.conf- ./ssl:/etc/nginx/ssldepends_on:- webvolumes:mysql_data:
13. 性能调优建议
数据库优化
-- 创建索引
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;
应用层优化
# 使用连接池
from sqlalchemy.pool import QueuePoolengine = create_engine(DATABASE_URL,poolclass=QueuePool,pool_size=20,max_overflow=30,pool_pre_ping=True,pool_recycle=3600
)# 使用缓存
from flask_caching import Cachecache = Cache(app, config={'CACHE_TYPE': 'redis','CACHE_REDIS_URL': 'redis://localhost:6379/0','CACHE_DEFAULT_TIMEOUT': 300
})# 异步任务处理
from celery import Celerycelery = 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类型提示
- 统一的错误处理
- 完整的日志记录