【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认证系统:

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类型提示
  • 统一的错误处理
  • 完整的日志记录

相关推荐
BYSJMG12 小时前
计算机Python毕业设计推荐:基于Django+Vue用户评论挖掘旅游系统
大数据·vue.js·hadoop·python·spark·django·课程设计
hvinsion12 小时前
零依赖每月工作计划备忘录:高效管理你的每一天
javascript·css·python·开源·html
二闹12 小时前
别再混淆了 is 和 ==的区别
后端·python
多恩Stone12 小时前
【3D 入门-4】trimesh 极速上手之 3D Mesh 数据结构解析(Vertices / Faces)
数据结构·人工智能·python·3d
多恩Stone13 小时前
【3D 入门-3】常见 3D 格式对比,.glb / .obj / .stl / .ply
人工智能·pytorch·python·深度学习·3d
先做个垃圾出来………13 小时前
242. 有效的字母异位词| 349. 两个数组的交集
开发语言·python
雷达学弱狗13 小时前
通用的二叉数迭代方法
python
青铜发条13 小时前
【python】python进阶——pip命令
python
njxiejing13 小时前
PyCharm社区版环境的安装(最新版2025.2.1)
ide·python·pycharm