学校学生作业管理系统完整实现方案


在这里插入图片描述

系统架构设计

技术栈选择

  • 后端: FastAPI (Python 3.10+)
  • 前端: Vue 3 + TypeScript + Element Plus
  • 数据库: PostgreSQL 14
  • 文件存储: MinIO (对象存储)
  • 实时通信: WebSocket
  • 搜索引擎: Elasticsearch (作业内容搜索)
  • 部署: Docker + Kubernetes (可选)

系统模块划分

.
├── backend
│   ├── app
│   │   ├── auth (认证授权)
│   │   ├── crud (数据库操作)
│   │   ├── db (数据库连接)
│   │   ├── models (ORM模型)
│   │   ├── schemas (Pydantic模型)
│   │   ├── services (核心业务逻辑)
│   │   ├── static (静态文件)
│   │   ├── utils (工具函数)
│   │   └── websocket (实时通信)
│   └── main.py
└── frontend
    ├── src
    │   ├── api (接口封装)
    │   ├── assets (静态资源)
    │   ├── components (公共组件)
    │   ├── composables (组合式函数)
    │   ├── router (路由)
    │   ├── stores (Pinia状态管理)
    │   ├── types (TS类型定义)
    │   ├── utils (工具函数)
    │   └── views (页面组件)
    └── vite.config.ts

数据库设计

核心数据表结构

  1. 用户基础表(users)
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    email VARCHAR(100) UNIQUE,
    phone VARCHAR(20),
    avatar_url VARCHAR(255),
    role ENUM('admin', 'teacher', 'student') NOT NULL,
    is_active BOOLEAN DEFAULT TRUE,
    last_login TIMESTAMP,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  1. 管理员扩展表(admins)
CREATE TABLE admins (
    user_id INT PRIMARY KEY REFERENCES users(id),
    department VARCHAR(100),
    position VARCHAR(100),
    permissions JSONB
);
  1. 教师扩展表(teachers)
CREATE TABLE teachers (
    user_id INT PRIMARY KEY REFERENCES users(id),
    teacher_no VARCHAR(20) UNIQUE,
    department VARCHAR(100),
    position VARCHAR(50),
    professional_title VARCHAR(50)
);
  1. 学生扩展表(students)
CREATE TABLE students (
    user_id INT PRIMARY KEY REFERENCES users(id),
    student_no VARCHAR(20) UNIQUE,
    class_id INT REFERENCES classes(id),
    enrollment_date DATE,
    advisor_id INT REFERENCES teachers(user_id)
);
  1. 班级表(classes)
CREATE TABLE classes (
    id SERIAL PRIMARY KEY,
    class_name VARCHAR(50) NOT NULL,
    grade VARCHAR(20),
    major VARCHAR(100),
    counselor_id INT REFERENCES teachers(user_id)
);
  1. 课程表(courses)
CREATE TABLE courses (
    id SERIAL PRIMARY KEY,
    course_code VARCHAR(20) UNIQUE NOT NULL,
    course_name VARCHAR(100) NOT NULL,
    credit NUMERIC(3,1),
    hours INT,
    semester VARCHAR(20),
    teacher_id INT REFERENCES teachers(user_id)
);
  1. 课程-学生关联表(course_students)
CREATE TABLE course_students (
    course_id INT REFERENCES courses(id),
    student_id INT REFERENCES students(user_id),
    PRIMARY KEY (course_id, student_id)
);
  1. 作业表(assignments)
CREATE TABLE assignments (
    id SERIAL PRIMARY KEY,
    title VARCHAR(200) NOT NULL,
    description TEXT,
    course_id INT REFERENCES courses(id),
    teacher_id INT REFERENCES teachers(user_id),
    deadline TIMESTAMP,
    total_score NUMERIC(5,1),
    attachment_url VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  1. 作业提交表(submissions)
CREATE TABLE submissions (
    id SERIAL PRIMARY KEY,
    assignment_id INT REFERENCES assignments(id),
    student_id INT REFERENCES students(user_id),
    content TEXT,
    attachment_url VARCHAR(255),
    submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    status ENUM('submitted', 'graded') DEFAULT 'submitted',
    score NUMERIC(5,1),
    feedback TEXT,
    graded_at TIMESTAMP,
    grader_id INT REFERENCES teachers(user_id)
);
  1. 平时成绩表(grades)
CREATE TABLE grades (
    course_id INT REFERENCES courses(id),
    student_id INT REFERENCES students(user_id),
    assignment_score NUMERIC(5,1) DEFAULT 0,
    participation_score NUMERIC(5,1) DEFAULT 0,
    total_score NUMERIC(5,1) GENERATED ALWAYS AS (assignment_score + participation_score) STORED,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (course_id, student_id)
);
  1. 系统公告表(announcements)
CREATE TABLE announcements (
    id SERIAL PRIMARY KEY,
    title VARCHAR(200) NOT NULL,
    content TEXT NOT NULL,
    publisher_id INT REFERENCES users(id),
    is_top BOOLEAN DEFAULT FALSE,
    status ENUM('draft', 'published') DEFAULT 'published',
    publish_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expire_time TIMESTAMP
);
  1. 系统配置表(configurations)
CREATE TABLE configurations (
    config_key VARCHAR(100) PRIMARY KEY,
    config_value JSONB NOT NULL,
    description TEXT
);

后端API实现 (FastAPI)

1. 认证授权模块

# auth/router.py
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
from app.auth.schemas import Token, UserCreate
from app.auth.service import (
    authenticate_user,
    create_access_token,
    get_current_active_user,
    create_user
)

router = APIRouter(tags=["认证授权"])

@router.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="用户名或密码错误")
    access_token = create_access_token(data={"sub": user.username})
    return {"access_token": access_token, "token_type": "bearer"}

@router.post("/register", status_code=201)
async def register(user_data: UserCreate):
    return create_user(user_data)

@router.get("/me")
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user

2. 管理员管理模块

# admin/router.py
from fastapi import APIRouter, Depends
from app.schemas.admin import AdminCreate, AdminUpdate
from app.services.admin import AdminService
from app.auth.dependencies import require_admin_role

router = APIRouter(prefix="/api/admin", tags=["管理员管理"], dependencies=[Depends(require_admin_role)])

@router.post("/", summary="创建管理员账号")
async def create_admin_account(admin_data: AdminCreate):
    return await AdminService.create_admin(admin_data)

@router.get("/", summary="获取管理员列表")
async def get_admin_list(page: int = 1, size: int = 10):
    return await AdminService.get_admin_list(page, size)

@router.put("/{admin_id}", summary="更新管理员信息")
async def update_admin_info(admin_id: int, update_data: AdminUpdate):
    return await AdminService.update_admin(admin_id, update_data)

@router.delete("/{admin_id}", summary="禁用管理员账号")
async def disable_admin_account(admin_id: int):
    return await AdminService.disable_admin(admin_id)

3. 作业管理模块

# assignment/router.py
from fastapi import APIRouter, UploadFile, File, Depends
from fastapi.responses import FileResponse
from app.schemas.assignment import AssignmentCreate, AssignmentUpdate
from app.services.assignment import AssignmentService
from app.auth.dependencies import require_teacher_role

router = APIRouter(prefix="/api/assignments", tags=["作业管理"])

@router.post("/", dependencies=[Depends(require_teacher_role)])
async def create_assignment(
    assignment_data: AssignmentCreate,
    attachment: UploadFile = File(None)
):
    return await AssignmentService.create_assignment(
        assignment_data, 
        attachment if attachment else None
    )

@router.get("/{assignment_id}")
async def get_assignment_details(assignment_id: int):
    return await AssignmentService.get_assignment(assignment_id)

@router.get("/course/{course_id}")
async def get_course_assignments(course_id: int):
    return await AssignmentService.get_course_assignments(course_id)

@router.put("/{assignment_id}", dependencies=[Depends(require_teacher_role)])
async def update_assignment(
    assignment_id: int,
    update_data: AssignmentUpdate,
    attachment: UploadFile = File(None)
):
    return await AssignmentService.update_assignment(
        assignment_id,
        update_data,
        attachment if attachment else None
    )

@router.get("/{assignment_id}/download")
async def download_assignment_attachment(assignment_id: int):
    file_path = await AssignmentService.get_assignment_file_path(assignment_id)
    return FileResponse(file_path)

4. 作业批改模块

# grading/router.py
from fastapi import APIRouter, Depends
from app.schemas.grading import SubmissionGrade, GradeUpdate
from app.services.grading import GradingService
from app.auth.dependencies import require_teacher_role

router = APIRouter(prefix="/api/grading", tags=["作业批改"])

@router.get("/submissions/{assignment_id}")
async def get_assignment_submissions(
    assignment_id: int,
    page: int = 1,
    size: int = 10,
    graded: bool = None
):
    return await GradingService.get_submissions(
        assignment_id, page, size, graded
    )

@router.post("/grade/{submission_id}", dependencies=[Depends(require_teacher_role)])
async def grade_submission(
    submission_id: int,
    grade_data: SubmissionGrade
):
    return await GradingService.grade_submission(submission_id, grade_data)

@router.put("/grade/{submission_id}", dependencies=[Depends(require_teacher_role)])
async def update_grade(
    submission_id: int,
    update_data: GradeUpdate
):
    return await GradingService.update_grade(submission_id, update_data)

@router.get("/stats/{assignment_id}")
async def get_assignment_grading_stats(assignment_id: int):
    return await GradingService.get_assignment_stats(assignment_id)

5. 平时成绩统计模块

# grade/router.py
from fastapi import APIRouter, Depends
from app.services.grade import GradeService
from app.auth.dependencies import get_current_user

router = APIRouter(prefix="/api/grades", tags=["成绩统计"])

@router.get("/course/{course_id}")
async def get_course_grades(
    course_id: int,
    student_id: int = None
):
    return await GradeService.get_course_grades(course_id, student_id)

@router.get("/student/{student_id}")
async def get_student_grades(student_id: int):
    return await GradeService.get_student_grades(student_id)

@router.get("/teacher/{teacher_id}")
async def get_teacher_course_grades(teacher_id: int):
    return await GradeService.get_teacher_course_grades(teacher_id)

@router.put("/participation", dependencies=[Depends(require_teacher_role)])
async def update_participation_score(
    course_id: int,
    student_id: int,
    score: float
):
    return await GradeService.update_participation_score(
        course_id, student_id, score
    )

前端Vue实现

核心页面实现

1. 作业提交页面 (学生)
<template>
  <div class="assignment-submit">
    <el-card>
      <template #header>
        <div class="card-header">
          <h2>{{ assignment.title }}</h2>
          <el-tag :type="deadlineStatus.type">
            {{ deadlineStatus.text }}
          </el-tag>
        </div>
      </template>

      <el-descriptions :column="1" border>
        <el-descriptions-item label="课程名称">
          {{ course.course_name }}
        </el-descriptions-item>
        <el-descriptions-item label="任课教师">
          {{ teacher.name }}
        </el-descriptions-item>
        <el-descriptions-item label="截止时间">
          {{ formatDateTime(assignment.deadline) }}
        </el-descriptions-item>
        <el-descriptions-item label="作业要求">
          <div v-html="assignment.description"></div>
        </el-descriptions-item>
        <el-descriptions-item label="附件" v-if="assignment.attachment_url">
          <el-button type="primary" link @click="downloadAttachment">
            下载作业附件
          </el-button>
        </el-descriptions-item>
      </el-descriptions>

      <el-divider />

      <el-form :model="form" label-width="120px" v-if="!isGraded">
        <el-form-item label="作业内容">
          <el-input
            v-model="form.content"
            type="textarea"
            :rows="8"
            placeholder="请输入作业内容"
          />
        </el-form-item>

        <el-form-item label="作业附件">
          <el-upload
            action="#"
            :auto-upload="false"
            :limit="3"
            :on-change="handleFileChange"
            :file-list="fileList"
          >
            <el-button type="primary">选择文件</el-button>
            <template #tip>
              <div class="el-upload__tip">
                支持PDF、Word、Zip等格式,单个文件不超过20MB
              </div>
            </template>
          </el-upload>
        </el-form-item>

        <el-form-item>
          <el-button
            type="primary"
            @click="submitAssignment"
            :loading="submitting"
          >
            提交作业
          </el-button>
          <el-button @click="saveDraft">保存草稿</el-button>
        </el-form-item>
      </el-form>

      <div v-else>
        <h3>批改结果</h3>
        <el-descriptions :column="1" border>
          <el-descriptions-item label="得分">
            <el-tag type="success" size="large">
              {{ submission.score }} / {{ assignment.total_score }}
            </el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="教师评语">
            {{ submission.feedback }}
          </el-descriptions-item>
          <el-descriptions-item label="批改时间">
            {{ formatDateTime(submission.graded_at) }}
          </el-descriptions-item>
        </el-descriptions>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { 
  getAssignmentDetail,
  submitAssignment,
  getStudentSubmission
} from '@/api/assignment'
import { downloadFile } from '@/api/file'
import { formatDateTime } from '@/utils/date'
import type { UploadFile } from 'element-plus'

const route = useRoute()
const assignmentId = parseInt(route.params.id as string)

// 作业数据
const assignment = ref<any>({})
const course = ref<any>({})
const teacher = ref<any>({})
const submission = ref<any>(null)
const fileList = ref<UploadFile[]>([])
const form = ref({
  content: '',
  attachments: [] as File[]
})
const submitting = ref(false)

// 获取作业详情
const fetchAssignment = async () => {
  const res = await getAssignmentDetail(assignmentId)
  assignment.value = res.data.assignment
  course.value = res.data.course
  teacher.value = res.data.teacher
  
  // 获取学生提交记录
  const subRes = await getStudentSubmission(assignmentId)
  if (subRes.data) {
    submission.value = subRes.data
    form.value.content = subRes.data.content
  }
}

// 计算截止时间状态
const deadlineStatus = computed(() => {
  if (!assignment.value.deadline) return { type: 'info', text: '无截止时间' }
  
  const now = new Date()
  const deadline = new Date(assignment.value.deadline)
  const diff = deadline.getTime() - now.getTime()
  
  if (diff <= 0) {
    return { type: 'danger', text: '已截止' }
  } else if (diff < 24 * 60 * 60 * 1000) {
    return { type: 'warning', text: '即将截止' }
  } else {
    return { type: 'success', text: '进行中' }
  }
})

// 是否已批改
const isGraded = computed(() => {
  return submission.value?.status === 'graded'
})

// 文件选择处理
const handleFileChange = (file: UploadFile) => {
  form.value.attachments.push(file.raw as File)
}

// 下载作业附件
const downloadAttachment = async () => {
  await downloadFile(assignment.value.attachment_url)
}

// 提交作业
const submitAssignment = async () => {
  submitting.value = true
  try {
    const formData = new FormData()
    formData.append('content', form.value.content)
    form.value.attachments.forEach(file => {
      formData.append('attachments', file)
    })
    
    await submitAssignment(assignmentId, formData)
    ElMessage.success('作业提交成功')
    await fetchAssignment()
  } finally {
    submitting.value = false
  }
}

// 保存草稿
const saveDraft = () => {
  // 实现草稿保存逻辑
  ElMessage.success('草稿保存成功')
}

onMounted(() => {
  fetchAssignment()
})
</script>
2. 作业批改页面 (教师)
<template>
  <div class="grading-page">
    <el-card>
      <template #header>
        <div class="card-header">
          <h2>作业批改 - {{ assignment.title }}</h2>
          <div>
            <el-tag>已提交: {{ stats.submitted }}</el-tag>
            <el-tag type="success">已批改: {{ stats.graded }}</el-tag>
            <el-tag type="info">未提交: {{ stats.unsubmitted }}</el-tag>
          </div>
        </div>
      </template>

      <div class="toolbar">
        <el-input
          v-model="searchQuery"
          placeholder="搜索学生姓名/学号"
          style="width: 300px"
          clearable
        >
          <template #append>
            <el-button :icon="Search" />
          </template>
        </el-input>
        
        <el-select
          v-model="filterStatus"
          placeholder="筛选状态"
          clearable
          style="width: 150px; margin-left: 10px"
        >
          <el-option label="已批改" value="graded" />
          <el-option label="未批改" value="ungraded" />
        </el-select>
        
        <el-button
          type="primary"
          @click="exportGrades"
          style="margin-left: auto"
        >
          导出成绩
        </el-button>
      </div>

      <el-table :data="filteredSubmissions" border style="width: 100%">
        <el-table-column prop="student.student_no" label="学号" width="120" />
        <el-table-column prop="student.name" label="姓名" width="100" />
        <el-table-column label="提交状态" width="100">
          <template #default="{ row }">
            <el-tag :type="row.submitted_at ? 'success' : 'danger'">
              {{ row.submitted_at ? '已提交' : '未提交' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="提交时间" width="180">
          <template #default="{ row }">
            {{ row.submitted_at ? formatDateTime(row.submitted_at) : '-' }}
          </template>
        </el-table-column>
        <el-table-column label="批改状态" width="100">
          <template #default="{ row }">
            <el-tag :type="row.status === 'graded' ? 'success' : 'warning'">
              {{ row.status === 'graded' ? '已批改' : '未批改' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="得分" width="120">
          <template #default="{ row }">
            <span v-if="row.status === 'graded'">
              {{ row.score }} / {{ assignment.total_score }}
            </span>
            <span v-else>-</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="180">
          <template #default="{ row }">
            <el-button
              size="small"
              @click="viewSubmission(row)"
              :disabled="!row.submitted_at"
            >
              查看
            </el-button>
            <el-button
              size="small"
              type="primary"
              @click="gradeSubmission(row)"
              :disabled="!row.submitted_at"
            >
              批改
            </el-button>
          </template>
        </el-table-column>
      </el-table>

      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :total="totalSubmissions"
        layout="total, sizes, prev, pager, next, jumper"
        :page-sizes="[10, 20, 50, 100]"
      />
    </el-card>

    <!-- 批改对话框 -->
    <grading-dialog
      v-model="dialogVisible"
      :submission="currentSubmission"
      :assignment="assignment"
      @graded="handleGraded"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { Search } from '@element-plus/icons-vue'
import { 
  getAssignmentSubmissions,
  getAssignmentStats,
  exportAssignmentGrades
} from '@/api/grading'
import GradingDialog from './components/GradingDialog.vue'
import { formatDateTime } from '@/utils/date'

const route = useRoute()
const assignmentId = parseInt(route.params.id as string)

// 表格数据
const submissions = ref<any[]>([])
const stats = ref({
  submitted: 0,
  graded: 0,
  unsubmitted: 0
})
const assignment = ref<any>({})
const currentPage = ref(1)
const pageSize = ref(10)
const searchQuery = ref('')
const filterStatus = ref('')

// 对话框控制
const dialogVisible = ref(false)
const currentSubmission = ref<any>(null)

// 获取作业提交列表
const fetchSubmissions = async () => {
  const res = await getAssignmentSubmissions(
    assignmentId,
    currentPage.value,
    pageSize.value,
    filterStatus.value === 'graded'
  )
  submissions.value = res.data.list
  assignment.value = res.data.assignment
}

// 获取作业统计信息
const fetchStats = async () => {
  const res = await getAssignmentStats(assignmentId)
  stats.value = res.data
}

// 过滤后的提交列表
const filteredSubmissions = computed(() => {
  let result = submissions.value
  if (searchQuery.value) {
    const query = searchQuery.value.toLowerCase()
    result = result.filter(
      sub =>
        sub.student.name.toLowerCase().includes(query) ||
        sub.student.student_no.toLowerCase().includes(query)
    )
  }
  return result
})

// 总提交数
const totalSubmissions = computed(() => {
  return filteredSubmissions.value.length
})

// 查看作业详情
const viewSubmission = (submission: any) => {
  currentSubmission.value = submission
  dialogVisible.value = true
}

// 批改作业
const gradeSubmission = (submission: any) => {
  currentSubmission.value = submission
  dialogVisible.value = true
}

// 批改完成处理
const handleGraded = () => {
  dialogVisible.value = false
  fetchSubmissions()
  fetchStats()
}

// 导出成绩
const exportGrades = async () => {
  await exportAssignmentGrades(assignmentId)
}

onMounted(() => {
  fetchSubmissions()
  fetchStats()
})

// 监听分页变化
watch([currentPage, pageSize], () => {
  fetchSubmissions()
})

// 监听筛选条件变化
watch(filterStatus, () => {
  currentPage.value = 1
  fetchSubmissions()
})
</script>

特色功能实现

1. 作业自动查重

# services/assignment.py
async def check_submission_similarity(submission_id: int):
    """
    使用文本相似度算法检查作业相似度
    """
    current_sub = await get_submission(submission_id)
    if not current_sub.content:
        return None
    
    # 获取同作业其他提交
    other_subs = await get_other_submissions(
        current_sub.assignment_id,
        exclude_id=submission_id
    )
    
    similarities = []
    for sub in other_subs:
        if not sub.content:
            continue
            
        # 使用TF-IDF计算相似度
        similarity = calculate_text_similarity(
            current_sub.content,
            sub.content
        )
        
        if similarity > 0.6:  # 相似度阈值
            similarities.append({
                'submission_id': sub.id,
                'student_name': sub.student.name,
                'student_no': sub.student.student_no,
                'similarity': round(similarity * 100, 2)
            })
    
    # 保存查重结果
    await save_similarity_result(submission_id, similarities)
    
    return sorted(similarities, key=lambda x: x['similarity'], reverse=True)

2. 成绩统计分析

# services/grade.py
async def get_course_grade_distribution(course_id: int):
    """
    获取课程成绩分布统计
    """
    grades = await get_course_grades(course_id)
    if not grades:
        return None
    
    # 分数段统计
    score_ranges = [0] * 5  # 0-59, 60-69, 70-79, 80-89, 90-100
    total_scores = [grade.total_score for grade in grades]
    
    for score in total_scores:
        if score < 60:
            score_ranges[0] += 1
        elif score < 70:
            score_ranges[1] += 1
        elif score < 80:
            score_ranges[2] += 1
        elif score < 90:
            score_ranges[3] += 1
        else:
            score_ranges[4] += 1
    
    # 计算统计指标
    avg_score = sum(total_scores) / len(total_scores)
    max_score = max(total_scores)
    min_score = min(total_scores)
    
    return {
        'score_ranges': score_ranges,
        'average_score': round(avg_score, 1),
        'max_score': max_score,
        'min_score': min_score,
        'student_count': len(total_scores)
    }

3. 实时作业提醒 (WebSocket)

# websocket/manager.py
class ConnectionManager:
    def __init__(self):
        self.active_connections: Dict[int, WebSocket] = {}
    
    async def connect(self, user_id: int, websocket: WebSocket):
        await websocket.accept()
        self.active_connections[user_id] = websocket
    
    def disconnect(self, user_id: int):
        if user_id in self.active_connections:
            del self.active_connections[user_id]
    
    async def send_personal_message(self, message: str, user_id: int):
        if user_id in self.active_connections:
            await self.active_connections[user_id].send_text(message)
    
    async def broadcast(self, message: str):
        for connection in self.active_connections.values():
            await connection.send_text(message)

# websocket/router.py
@router.websocket("/ws/{user_id}")
async def websocket_endpoint(
    websocket: WebSocket,
    user_id: int,
    token: str,
    manager: ConnectionManager = Depends(get_manager)
):
    # 验证token
    if not verify_websocket_token(token, user_id):
        await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
        return
    
    await manager.connect(user_id, websocket)
    try:
        while True:
            # 保持连接
            await websocket.receive_text()
    except WebSocketDisconnect:
        manager.disconnect(user_id)

系统安全与优化

安全措施

  1. API安全

    • JWT认证 + CSRF保护
    • 敏感操作审计日志
    • 请求速率限制 (使用FastAPI的slowapi)
  2. 数据安全

    • 数据库字段级加密 (如密码、敏感信息)
    • 定期数据备份
    • 文件上传病毒扫描
  3. 权限控制

# auth/dependencies.py
def require_admin_role(current_user: User = Depends(get_current_active_user)):
    if current_user.role != 'admin':
        raise HTTPException(
            status_code=403,
            detail="只有管理员可以访问此功能"
        )
    return current_user

def require_teacher_role(current_user: User = Depends(get_current_active_user)):
    if current_user.role != 'teacher':
        raise HTTPException(
            status_code=403,
            detail="只有教师可以访问此功能"
        )
    return current_user

性能优化

  1. 数据库优化

    • 常用查询添加索引
    • 复杂查询使用物化视图
    • 读写分离
  2. 缓存策略

    • Redis缓存热点数据 (如公告、配置)
    • 作业列表缓存
  3. 异步处理

    • 使用Celery处理耗时任务 (如作业查重、成绩导出)

部署方案

Docker Compose 配置

version: '3.8'

services:
  backend:
    build: ./backend
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/app_db
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis
      - minio

  frontend:
    build: ./frontend
    ports:
      - "8080:80"
    depends_on:
      - backend

  db:
    image: postgres:14
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=app_db
    volumes:
      - pg_data:/var/lib/postgresql/data

  redis:
    image: redis:6
    volumes:
      - redis_data:/data

  minio:
    image: minio/minio
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      - MINIO_ROOT_USER=minioadmin
      - MINIO_ROOT_PASSWORD=minioadmin
    volumes:
      - minio_data:/data
    command: server /data --console-address ":9001"

  celery:
    build: ./backend
    command: celery -A app.celery_app worker --loglevel=info
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/app_db
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis

volumes:
  pg_data:
  redis_data:
  minio_data:

扩展功能路线图

  1. 移动端支持

    • 开发响应式Web界面
    • 开发微信小程序版本
  2. AI辅助批改

    • 使用NLP技术自动评分
    • 语法错误自动检测
  3. 学习分析

    • 学生学习行为分析
    • 作业完成时间预测
  4. 多维度评价

    • 同学互评功能
    • 多维度评分体系
评论 112
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百锦再@新空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值