基于Flask框架实现的一个在线考试系统

文章目录

  • 1.学习前提
    • [1.1 flask框架学习](#1.1 flask框架学习)
    • [1.2 vscode配置python环境](#1.2 vscode配置python环境)
    • [1.3 flask框架实现orm操作](#1.3 flask框架实现orm操作)
      • [1.3.1 基础查询get、filter、filter_by、order_by、with_entities](#1.3.1 基础查询get、filter、filter_by、order_by、with_entities)
      • [1.3.2 数据记录 first、all、one、limit、offset、count、paginate](#1.3.2 数据记录 first、all、one、limit、offset、count、paginate)
  • 2.项目展示
  • [3 sqlite3数据库表结构 py-orm展示](#3 sqlite3数据库表结构 py-orm展示)
  • 4.后端设计
    • [4.1. 目录结构](#4.1. 目录结构)
    • [4.2. 核心模块说明](#4.2. 核心模块说明)
      • [4.2.1 应用工厂 (app/init.py)](#4.2.1 应用工厂 (app/init.py))
      • [4.2.2 配置管理 (config.py)](#4.2.2 配置管理 (config.py))
      • [4.2.3 数据库模型 (app/models.py)](#4.2.3 数据库模型 (app/models.py))
      • [4.2.4 API 接口 (app/api/)](#4.2.4 API 接口 (app/api/))
      • [4.2.5 权限控制 (app/decorators.py)](#4.2.5 权限控制 (app/decorators.py))
      • [4.2.6 工具类 (app/utils.py)](#4.2.6 工具类 (app/utils.py))
  • 5.前端设计
    • [5.1. 技术栈](#5.1. 技术栈)
    • [5.2. 目录结构](#5.2. 目录结构)
    • [5.3. 核心模块](#5.3. 核心模块)
      • [5.3.1 认证与权限](#5.3.1 认证与权限)
      • [5.3.2 状态管理](#5.3.2 状态管理)
      • [5.4.3 网络请求](#5.4.3 网络请求)
      • [5.4.4 核心组件](#5.4.4 核心组件)
    • [5.4 管理员页面](#5.4 管理员页面)
    • [5.5 用户页面](#5.5 用户页面)
  • 6.最后
    • [6.1 参考](#6.1 参考)
    • [6.2 总结](#6.2 总结)

1.学习前提

项目情况

  • Python3.12.9+
  • Vue 3
  • Redis
  • SqlLite3

1.1 flask框架学习

推荐菜鸟编程:https://www.runoob.com/flask/flask-tutorial.html

基本上用一下午的时间过一遍,就能快速写出一个web小程序了,这里编译器推荐vscode,不推荐pycharm,因为我个人认为vscode更轻量,用i起来可以根据自己的习惯去配置。

1.2 vscode配置python环境

1.创建虚拟环境并激活。

2.vscode选择虚拟环境之后,即自动激活,通过 pip install -r requirements.txt 下载flask框架,并创建web测试程序。

如上,一个简易的flask项目就i已经完成了。

但是再写一个web项目的时候,可能需要涉及到数据库、缓存,但是我也相信,既然你看到这里了,那么肯定是有一定基础了。

所以,不再过多赘述。

1.3 flask框架实现orm操作

如上图,为什么使用User 模型类去调用query呢?

py 复制代码
from flask import Flask
from flask_bootstrap import Bootstrap5
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_mail import Mail
from flask_cors import CORS
from flask_jwt_extended import JWTManager
from redis import Redis
from config import config


bootstrap = Bootstrap5()
db = SQLAlchemy()
mail = Mail()
jwt = JWTManager()
cors = CORS()

这是因为,我们引入了flask_sqlalchemy其实是可以使用db.session.query(Model)来操作的,但是每一个模型类型又继承了db.Model,所以可以直接使用这些类名去调用相关sql方法。

1.3.1 基础查询get、filter、filter_by、order_by、with_entities

sql 复制代码
# 1.get按照主键查询
user = User.query.get(1)  # 查询 ID 为 1 的用户(存在返回实例,不存在返回 None)  

# 2.filter复杂的条件查询
users = User.query.filter(  
    User.registered_at > datetime(2023, 1, 1),  
    User.email.like('%@example.com')  
).all() 

# 3.filter_by等值查询,无法使用区间匹配、模糊搜索等
active_users = User.query.filter_by(is_active=True).all()  # 查询所有活跃用户  

# 4.order_by排序
# 5.withentitiex 类似 于 SELLETCT

1.3.2 数据记录 first、all、one、limit、offset、count、paginate

学过的Go语言的gORM的其实对以上的一些功能也有同感,可惜没有类似Last、Take的功能,但是first、one这些足够用了。具体功能不做解释了,可以自行搜一下。

2.项目展示

管理员页面

用户页面

3 sqlite3数据库表结构 py-orm展示

3.1 权限管理

py 复制代码
class Permission:
    TAKE_EXAM = 0x01
    VIEW_HISTORY = 0x02
    CREATE_EXAM = 0x04
    GRADE_EXAM = 0x08
    ADMINISTER = 0x80

这是一个权限类,我才用16进制的方式,管理每一个用户的对应权限。这样做的好处就是通过一个INTERGER的字段就可以表示这个用户的权限。避免多列表方式,简单高效。

1.比如多权限用户

py 复制代码
# 赋予用户:参加考试(0x01) 和 查看历史(0x02)
user_permissions = Permission.TAKE_EXAM | Permission.VIEW_HISTORY

# 结果:0x03 (二进制 0000 0011)
0000 0001 (TAKE_EXAM)
0000 0010 (VIEW_HISTORY)
-----------
0000 0011 (结果 0x03)

2.检查用户权限

py 复制代码
# 1.没有权限
0000 0011 (用户只有 0x01 和 0x02)
0000 1000 (我们要查的是 0x08)
-----------
0000 0000 (结果是 0,在 Python 中代表 False)

# 2.有权限
0000 0011 (用户有 0x01 和 0x02)
0000 0001 (我们要查的是 0x01)
-----------
0000 0001 (结果是 0x01,不为 0,在 Python 中代表 True)

3.移除某个权限

py 复制代码
# 假设用户目前有:考试(0x01) 和 看历史(0x02),总值是 0x03
user_perm = 0x03 
# 现在我们要【删除】考试权限 (0x01)
user_perm = user_perm & ~Permission.TAKE_EXAM
# 结果:user_perm 变成了 0x02 (只剩下看历史了)
0000 0011 (用户原有的: 考试 + 历史)
1111 1110 (除了考试位,其他全都要) # 对原有进制取非,在进行且运算即可
-----------
0000 0010 (结果:只剩下了历史)

3.2 models

py 复制代码
import json
from flask import current_app
from flask_login import UserMixin
from . import db, login_manager
from .utils import TimeUtil


class Permission:
    TAKE_EXAM = 0x01
    VIEW_HISTORY = 0x02
    CREATE_EXAM = 0x04
    GRADE_EXAM = 0x08
    ADMINISTER = 0x80


class UserType(db.Model):
    __tablename__ = "user_types"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True, index=True)
    users = db.relationship(
        "User",
        backref="user_type",
        lazy="dynamic",
        primaryjoin="UserType.id==User.user_type_id",
        foreign_keys="User.user_type_id",
    )

    @staticmethod
    def insert_default_types():
        types = ["默认分组"]
        for t in types:
            if not UserType.query.filter_by(name=t).first():
                db.session.add(UserType(name=t))
        db.session.commit()

    def to_dict(self):
        return {
            "id": self.id,
            "name": self.name,
        }


# 关联表:考试 - 用户类型
exam_user_types = db.Table(
    "exam_user_types",
    db.Column("exam_id", db.Integer, db.ForeignKey("exams.id"), primary_key=True),
    db.Column(
        "user_type_id", db.Integer, db.ForeignKey("user_types.id"), primary_key=True
    ),
)


class Role(db.Model):
    __tablename__ = "roles"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    default = db.Column(db.Boolean, default=False, index=True)
    permissions = db.Column(db.Integer)
    users = db.relationship(
        "User",
        backref="role",
        lazy="dynamic",
        foreign_keys="User.role_id",
        primaryjoin="Role.id==User.role_id",
    )

    @staticmethod
    def insert_roles():
        roles = {
            "用户": (Permission.TAKE_EXAM | Permission.VIEW_HISTORY, True),
            "管理员": (0xFF, False),
        }
        for r in roles:
            role = Role.query.filter_by(name=r).first()
            if role is None:
                role = Role(name=r)
            role.permissions = roles[r][0]
            role.default = roles[r][1]
            db.session.add(role)
        db.session.commit()

    def __repr__(self):
        return "<Role %r>" % self.name


class User(UserMixin, db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), index=True)
    role_id = db.Column(db.Integer)
    user_type_id = db.Column(db.Integer)
    status = db.Column(db.String(20), default="pending")
    created_at = db.Column(db.DateTime(), default=TimeUtil.now)
    submissions = db.relationship(
        "Submission",
        backref="student",
        lazy="dynamic",
        cascade="all, delete-orphan",
        foreign_keys="Submission.user_id",
        primaryjoin="User.id==Submission.user_id",
    )

    def __init__(self, **kwargs):
        super(User, self).__init__(**kwargs)
        if self.role is None:
            if self.email == current_app.config["FLASKY_ADMIN"]:
                self.role = Role.query.filter_by(permissions=0xFF).first()
                self.status = "approved"
            if self.role is None:
                self.role = Role.query.filter_by(default=True).first()
        if self.user_type_id is None:
            pass

    def can(self, permissions):
        return (
            self.role is not None
            and (self.role.permissions & permissions) == permissions
        )

    def is_administrator(self):
        return self.can(Permission.ADMINISTER)

    def to_dict(self):
        return {
            "id": self.id,
            "userId": self.id,
            "email": self.email,
            "userName": self.username,
            "role": self.role.name if self.role else None,
            "userType": self.user_type.name if self.user_type else None,
            "userTypeId": self.user_type_id,
            "status": self.status,  # pending, approved, rejected
            "createTime": TimeUtil.format(self.created_at),
        }

    def __repr__(self):
        return "<User %r>" % self.email


@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))


class QuestionType:
    CHOICE = "choice"
    ESSAY = "essay"
    CODE = "code"


class Question(db.Model):
    __tablename__ = "questions"
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text)
    question_type = db.Column(db.String(20))  # choice, essay, code
    points = db.Column(db.Integer, default=10)
    extra_data = db.Column(db.Text)  # JSON: options, test_cases
    created_at = db.Column(db.DateTime(), default=TimeUtil.now)
    exams = db.relationship(
        "ExamQuestion",
        back_populates="question",
        cascade="all, delete-orphan",
        primaryjoin="Question.id==ExamQuestion.question_id",
        foreign_keys="ExamQuestion.question_id",
    )

    def get_extra(self):
        try:
            return json.loads(self.extra_data) if self.extra_data else {}
        except:
            return {}

    def set_extra(self, data):
        self.extra_data = json.dumps(data)

    def to_dict(self):
        return {
            "id": self.id,
            "content": self.content,
            "question_type": self.question_type,
            "points": self.points,
            "extra_data": self.get_extra(),
            "created_at": TimeUtil.format(self.created_at),
        }


class Exam(db.Model):
    __tablename__ = "exams"
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(128))
    description = db.Column(db.Text)
    duration_minutes = db.Column(db.Integer, default=60)
    start_time = db.Column(db.DateTime)
    status = db.Column(db.String(20), default="draft")
    created_by = db.Column(db.Integer)
    created_at = db.Column(db.DateTime(), default=TimeUtil.now)
    settings = db.Column(db.Text, default="{}")
    allowed_user_types = db.relationship(
        "UserType",
        secondary=exam_user_types,
        lazy="subquery",
        backref=db.backref("exams", lazy=True),
        primaryjoin="Exam.id==exam_user_types.c.exam_id",
        secondaryjoin="UserType.id==exam_user_types.c.user_type_id",
    )

    questions = db.relationship(
        "ExamQuestion",
        back_populates="exam",
        cascade="all, delete-orphan",
        primaryjoin="Exam.id==ExamQuestion.exam_id",
        foreign_keys="ExamQuestion.exam_id",
    )
    submissions = db.relationship(
        "Submission",
        backref="exam",
        lazy="dynamic",
        foreign_keys="Submission.exam_id",
        primaryjoin="Exam.id==Submission.exam_id",
    )

    def get_settings(self):
        try:
            return json.loads(self.settings) if self.settings else {}
        except:
            return {}

    def to_dict(self):
        return {
            "id": self.id,
            "title": self.title,
            "description": self.description,
            "duration_minutes": self.duration_minutes,
            "start_time": TimeUtil.format(self.start_time),
            "status": self.status,
            "allowed_user_types": [ut.id for ut in self.allowed_user_types],
            "allowed_user_type_names": [ut.name for ut in self.allowed_user_types],
            "created_by": self.created_by,
            "created_at": TimeUtil.format(self.created_at),
            "settings": self.get_settings(),
        }


class ExamQuestion(db.Model):
    __tablename__ = "exam_questions"
    exam_id = db.Column(db.Integer, db.ForeignKey("exams.id"), primary_key=True)
    question_id = db.Column(db.Integer, db.ForeignKey("questions.id"), primary_key=True)
    order = db.Column(db.Integer, default=0)
    score = db.Column(db.Integer)

    exam = db.relationship(
        "Exam",
        back_populates="questions",
        primaryjoin="Exam.id==ExamQuestion.exam_id",
        foreign_keys="ExamQuestion.exam_id",
    )
    question = db.relationship(
        "Question",
        back_populates="exams",
        primaryjoin="Question.id==ExamQuestion.question_id",
        foreign_keys="ExamQuestion.question_id",
    )


class Submission(db.Model):
    __tablename__ = "submissions"
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer)
    exam_id = db.Column(db.Integer)
    start_time = db.Column(db.DateTime, default=TimeUtil.now)
    submit_time = db.Column(db.DateTime)
    total_score = db.Column(db.Float, default=0.0)
    status = db.Column(db.String(20), default="pending")
    admin_feedback = db.Column(db.Text)
    answers = db.relationship(
        "SubmissionAnswer",
        backref="submission",
        lazy="dynamic",
        cascade="all, delete-orphan",
        foreign_keys="SubmissionAnswer.submission_id",
        primaryjoin="Submission.id==SubmissionAnswer.submission_id",
    )

    def to_dict(self, include_answers=False):
        data = {
            "id": self.id,
            "user_id": self.user_id,
            "exam_id": self.exam_id,
            "start_time": TimeUtil.format(self.start_time),
            "submit_time": TimeUtil.format(self.submit_time),
            "total_score": self.total_score,
            "status": self.status,
            "admin_feedback": self.admin_feedback,
            "student_name": self.student.username if self.student else None,
            "exam_title": self.exam.title if self.exam else None,
        }
        if include_answers:
            data["answers"] = [ans.to_dict() for ans in self.answers]
        return data


class SubmissionAnswer(db.Model):
    __tablename__ = "submission_answers"
    id = db.Column(db.Integer, primary_key=True)
    submission_id = db.Column(db.Integer)
    question_id = db.Column(db.Integer)
    content = db.Column(db.Text)
    score = db.Column(db.Float, default=0.0)
    comment = db.Column(db.Text)
    question = db.relationship(
        "Question",
        foreign_keys="SubmissionAnswer.question_id",
        primaryjoin="Question.id==SubmissionAnswer.question_id",
    )

    def to_dict(self):
        return {
            "id": self.id,
            "submission_id": self.submission_id,
            "question_id": self.question_id,
            "content": self.content,
            "score": self.score,
            "comment": self.comment,
            "question": self.question.to_dict() if self.question else None,
        }

3.3 数据库设计

在线考试系统,主要包含三个核心模块:用户管理(用户、角色、用户组)、考试管理(考试、题目)以及考试执行(答卷、答题详情)。

  • 用户 (User) 属于一个 角色 (Role) 和一个 用户组 (UserType)
  • 考试 (Exam) 包含多个 题目 (Question) (通过 ExamQuestion 表进行多对多关联,并包含题目顺序和分值信息)。
  • 考试 (Exam) 可以授权给多个 用户组 (UserType) 参加(多对多关联)。
  • 用户 (User) 参加考试会产生 答卷 (Submission)
  • 答卷 (Submission) 包含多个 答题详情 (SubmissionAnswer)

3.3.1. 用户管理模块

users (用户表)
字段名 类型 约束 作用描述
id Integer 主键 用户的唯一标识符
email String(64) 唯一, 索引 用户的邮箱地址,用于登录系统
username String(64) 索引 用户的显示名称
role_id Integer 外键 -> roles.id 关联的角色ID,决定用户的权限(如管理员、普通用户)
user_type_id Integer 外键 -> user_types.id 关联的用户组ID,用于考试的分组授权
status String(20) 默认: 'pending' 账号状态(例如:'pending'-待审核, 'approved'-已批准)
created_at DateTime 账号创建时间
roles (角色表)
字段名 类型 约束 作用描述
id Integer 主键 角色的唯一标识符
name String(64) 唯一 角色名称(如 '管理员')
default Boolean 默认: False, 索引 是否为新注册用户的默认角色
permissions Integer 权限位掩码,用于控制具体操作权限(如创建考试、阅卷等)
user_types(用户组/类型表)
字段名 类型 约束 作用描述
id Integer 主键 用户组的唯一标识符
name String(64) 唯一, 索引 用户组名称

3.2.1. 考试管理模块

exams (考试表)
字段名 类型 约束 作用描述
id Integer 主键 考试的唯一标识符
title String(128) 考试标题
description Text 考试说明或指导语
duration_minutes Integer 默认: 60 考试时长限制(分钟)
start_time DateTime 考试开始时间
status String(20) 默认: 'draft' 考试状态('draft'-草稿, 'published'-已发布, 'ongoing'-进行中)
created_by Integer 外键 -> users.id 创建该考试的用户ID
created_at DateTime 考试创建时间
settings Text 默认: '{}' JSON 格式的扩展配置
questions (题库表)
字段名 类型 约束 作用描述
id Integer 主键 题目的唯一标识符
content Text 题干内容(支持 HTML/富文本)
question_type String(20) 题目类型
points Integer 默认: 10 题目的默认分值
extra_data Text JSON 格式的额外数据(如选择题的选项、编程题的测试用例)
created_at DateTime 题目创建时间
exam_questions (试卷-题目关联表)
字段名 类型 约束 作用描述
exam_id Integer 主键 , 外键 -> exams.id 关联的考试ID
question_id Integer 主键 , 外键 -> questions.id 关联的题目ID
order Integer 默认: 0 题目在试卷中的显示顺序
score Integer 该题目在本次考试中的具体分值
exam_user_types(考试-权限关联表)
字段名 类型 约束 作用描述
exam_id Integer 主键 , 外键 -> exams.id 关联的考试ID
user_type_id Integer 主键 , 外键 -> user_types.id 允许参加考试的用户组ID

3.3.3. 评分与答卷模块

submissions(答卷表)
字段名 类型 约束 作用描述
id Integer 主键 答卷的唯一标识符
user_id Integer 外键 -> users.id 考生的用户ID
exam_id Integer 外键 -> exams.id 参加的考试ID
start_time DateTime 考生开始答题的时间
submit_time DateTime 考生交卷的时间
total_score Float 默认: 0.0 答卷的总得分
status String(20) 默认: 'pending' 阅卷状态('pending'-待批改, 'graded'-已批改)
admin_feedback Text 老师对整张试卷的评语
submission_answers (答题详情表)
字段名 类型 约束 作用描述
id Integer 主键 答题记录的唯一标识符
submission_id Integer 外键 -> submissions.id 关联的答卷ID
question_id Integer 外键 -> questions.id 关联的题目ID
content Text 考生的作答内容(文本、选项值或代码)
score Float 默认: 0.0 该题的得分
comment Text 老师对该题的单题评语

3.3.4. 权限标识数据

  • TAKE_EXAM (0x01): 参加考试
  • VIEW_HISTORY (0x02): 查看考试记录
  • CREATE_EXAM (0x04): 创建考试
  • GRADE_EXAM (0x08): 阅卷
  • ADMINISTER (0x80): 系统管理
  • CHOICE: 选择题
  • ESSAY: 问答题(简答/论述)
  • CODE: 编程题

4.后端设计

Flask-exampy 是一个基于 Flask 框架构建的在线考试系统后端。它提供了一整套 RESTful API,用于处理用户认证、考试管理、题目管理、在线答题及自动阅卷等核心功能。

4.1. 目录结构

复制代码
backend/
├── app/                        # 应用核心代码
│   ├── api/                    # API 接口模块(蓝图)
│   │   ├── auth.py             # 认证相关接口 (登录, 注册, 令牌刷新)
│   │   ├── exam.py             # 考试管理接口 (创建, 发布, 列表, 详情)
│   │   ├── question.py         # 题目管理接口 (增删改查)
│   │   ├── system.py           # 系统管理接口 (用户管理, 角色管理)
│   │   ├── common.py           # 公共接口
│   │   └── ...
│   ├── templates/              # 邮件模板
│   ├── models.py               # 数据库模型定义
│   ├── email.py                # 邮件发送逻辑 
│   ├── utils.py                # 通用工具类
│   ├── decorators.py           # 自定义装饰器
│   └── __init__.py             # 应用工厂函数 create_app
├── configs/                    # 配置文件
│   ├── dev.yaml                # 开发环境配置
│   └── prod.yaml               # 生产环境配置
├── migrations/                 # 数据库迁移脚本 
├── config.py                   # 配置加载器
├── manage.py                   # CLI 入口脚本
├── Dockerfile                  # 容器构建文件
└── requirements.txt            # Python 依赖列表

4.2. 核心模块说明

4.2.1 应用工厂 (app/init.py)

  • create_app(config_name): 初始化 Flask 应用的核心函数。
  • 负责初始化所有扩展:数据库 (db)、邮件 (mail)、JWT (jwt)、Redis (redis_client) 等。
  • 注册 API 蓝图。
  • 启动后台调度线程 (_start_scheduler),用于定期检查考试状态。

4.2.2 配置管理 (config.py)

  • 支持从 YAML 文件 (configs/dev.yaml, configs/prod.yaml) 加载配置。
  • 定义了 DevelopmentConfigProductionConfig,通过 YAML 配置进行切换。
  • 配置项包括数据库 URI、Redis 地址、邮件服务器设置、密钥等。

4.2.3 数据库模型 (app/models.py)

  • 用户体系 : User, Role, UserType (支持基于角色的权限控制 RBAC 和基于用户组的访问控制)。
  • 考试体系 : Exam, Question, ExamQuestion (关联表,支持题目顺序和分值自定义)。
  • 答卷体系 : Submission, SubmissionAnswer (记录考生的答题情况和得分)。

4.2.4 API 接口 (app/api/)

  • auth.py: 处理用户注册、登录、获取当前用户信息。使用 JWT 进行身份验证。
  • system.py: 管理员专用接口,用于管理用户列表、修改用户分组、审核账号等。
  • question.py: 题库管理,支持选择题、简答题、编程题的增删改查。
  • exam.py: 考试全生命周期管理(创建草稿 -> 发布 -> 进行中 -> 结束 -> 阅卷)。

4.2.5 权限控制 (app/decorators.py)

  • @permission_required: 检查用户是否拥有特定权限位。
  • @admin_required: 专门用于保护需要管理员权限的接口。

4.2.6 工具类 (app/utils.py)

  • RedisWrapper: 封装 Redis 操作。
  • TimeUtil: 统一处理时间时区问题(强制使用上海时间)。

5.前端设计

前端使用框架:https://gitee.com/y_project/RuoYi-Vue/tree/master/ruoyi-ui

5.1. 技术栈

技术 说明 版本
Vue.js 核心前端框架 2.6.12
Element UI 桌面端组件库 2.15.14
Vuex 状态管理模式 3.6.0
Vue Router 路由管理器 3.4.9
Axios HTTP 客户端 0.28.1
Vue CLI 脚手架工具 4.4.6

5.2. 目录结构

text 复制代码
web/
├── bin/                # 构建脚本
├── public/             # 静态资源 (favicon, index.html)
├── src/                # 源代码目录
│   ├── api/            # 后端 API 接口定义
│   ├── assets/         # 静态资源
│   ├── components/     # 全局公共组件
│   ├── directive/      # 自定义指令
│   ├── layout/         # 布局组件 (Sidebar, Navbar, AppMain)
│   ├── plugins/        # 插件 
│   ├── router/         # 路由配置
│   ├── store/          # Vuex 状态管理
│   ├── utils/          # 工具类 
│   ├── views/          # 页面组件
│   ├── App.vue         # 根组件
│   ├── main.js         # 入口文件
│   ├── permission.js   # 权限控制
│   └── settings.js     # 全局配置
├── .env.development    # 开发环境变量
├── .env.production     # 生产环境变量
├── babel.config.js     # Babel 配置
├── package.json        # 项目依赖配置
└── vue.config.js       # Vue CLI 配置

5.3. 核心模块

5.3.1 认证与权限

  • Token 机制: 系统采用 JWT Token 进行身份验证。
  • 存储 : Token 存储在 Cookie 中 (utils/auth.js)。
  • 请求拦截 : utils/request.js 中的 Axios 拦截器会自动在每个请求头中添加 Authorization: Bearer <token>
  • 路由守卫 : permission.js 实现了全局路由守卫 (router.beforeEach),负责:
    • 检查用户是否已登录。
    • 获取用户信息和权限角色。
    • 根据权限动态生成路由表。

5.3.2 状态管理

Store 位于 src/store,主要模块包括:

  • user.js: 管理登录状态、Token、用户信息、角色权限。
  • permission.js: 管理动态路由表,根据后端返回的权限生成可访问的菜单。
  • app.js: 管理侧边栏收缩状态、设备类型等 UI 状态。
  • settings.js: 管理系统布局配置。

5.4.3 网络请求

  • 封装 : utils/request.js 对 Axios 进行了二次封装。
  • 统一错误处理: 自动处理 401 (未授权)、403 (禁止访问)、500 (服务器错误) 等状态码,并提示错误信息。
  • API 管理 : 所有 API 请求按业务模块集中管理在 src/api 目录下,例如 src/api/exam.js 包含所有考试相关的接口。

5.4.4 核心组件

  • Layout : 位于 src/layout,包含侧边栏 (Sidebar)、顶部导航 (Navbar) 和主内容区 (AppMain)。
  • Editor : 富文本编辑器组件 (src/components/Editor),基于 Quill 实现。
  • Pagination : 封装的 Element UI 分页组件 (src/components/Pagination),统一了分页样式和逻辑。

5.4 管理员页面

管理员页面功能描述:

  • 审核用户申请功能
  • 能够发布相关考试题目
  • 进行阅卷批改评论
  • 查看所有用户答卷
  • 待补充...

5.5 用户页面

  • 我的考试
  • 考试记录

6.最后

6.1 参考

1.https://www.runoob.com/flask/flask-tutorial.html

2.https://gitee.com/y_project/RuoYi-Vue/tree/master/ruoyi-ui

3.https://gemini.google.com/

6.2 总结

本系统是一个基于 Flask (后端)Vue.js (前端) 的在线考试管理系统。经过近期的维护和重构,系统目前处于稳定状态。核心的考试管理、题库管理、用户认证等功能均正常运行。

学习并应用Flask框架,做了这么一个web项目,后续继续学习Django然后重新做一个项目,大家就尽情期待吧。

相关推荐
Algebraaaaa2 小时前
为什么线程阻塞要用.join而不是.wait
java·c++·python
王中阳Go2 小时前
12 Go Eino AI应用开发实战 | 消息队列架构
人工智能·后端·go
起风了___2 小时前
Python 批量发邮件脚本:Excel 名单 + Jinja2 模板,带日志与防限流,163 邮箱实测可用
python·程序员
沐森2 小时前
Rust 的CPU和IO操作
后端
Lucky_Turtle2 小时前
【Springboot】解决PageHelper在实体转Vo下出现total数据问题
java·spring boot·后端
Mr.朱鹏2 小时前
大模型入门学习路径(Java开发者版)下
java·python·学习·微服务·langchain·大模型·llm
無量2 小时前
AI工程化实践指南:从入门到落地
后端·ai编程
golang学习记2 小时前
Jetbrains 这个知名软件十年了!
后端
老华带你飞2 小时前
志愿者服务管理|基于springboot 志愿者服务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring