跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI

基于 Python 的毕业设计管理系统架构与 AI 辅助开发实践

介绍基于 Python 的毕业设计管理系统架构,采用 FastAPI 和 PostgreSQL 解决选题冲突、进度黑盒及权限混乱问题。通过 JWT 鉴权和防重复提交逻辑保障安全,并分享利用 AI 工具辅助编写代码、事务逻辑及测试用例的实战经验,提供生产环境部署建议。

云间运维发布于 2026/3/22更新于 2026/9/1168 浏览

1. 先聊聊痛点:毕业设计管理为啥需要系统化?

在动手之前,我们得先搞清楚要解决什么问题。传统的毕业设计管理,很多学校还停留在 Excel 表格、微信群和邮件往来的阶段,这里面槽点实在太多了:

  • 选题冲突严重:学生和导师之间信息不对称,经常出现'一个课题多人抢'或者'好导师被秒光'的情况,全靠手速和关系,很不公平。
  • 过程进度黑盒:开题、中期、答辩这些关键节点,学生提交了啥材料,导师批阅意见是什么,管理员很难全局掌握,催进度基本靠吼。
  • 权限管理混乱:学生误操作了导师的审核界面,管理员不小心删了数据,角色权限划分不清晰,容易出乱子。
  • 文档版本灾难:论文、报告反复修改,不同版本的文件散落在各个聊天记录和邮箱里,查找和归档简直是噩梦。

所以,我们的系统核心目标就是:流程线上化、操作透明化、权限精细化、文档集中化。

2. 技术选型:为什么是 FastAPI + SQLAlchemy?

确定了目标,接下来就是选择趁手的工具。这里主要对比了两个主流方案:

Web 框架:Django vs FastAPI

  • Django:大而全,自带 Admin 后台、ORM、用户认证,开箱即用。如果追求快速搭建一个管理后台,Django 非常合适。但它的'全家桶'模式有时也显得笨重,灵活性稍差,对于需要精细控制 API 行为和性能的场景,可能不是最轻量的选择。
  • FastAPI:现代、异步优先、性能极高,并且自动生成交互式 API 文档(Swagger UI)。我们的系统核心是提供清晰的 RESTful API 给前端(比如 Vue/React),FastAPI 的声明式依赖注入系统让实现 JWT 认证、角色权限检查变得异常优雅和模块化。最终我选择了 FastAPI,看中的就是它的高性能、清晰的代码结构以及对 OpenAPI 的原生支持。

数据库:关系型 vs 文档型

  • 关系型数据库(如 PostgreSQL/MySQL):数据结构规整,存在明确的导师、学生、课题、任务、提交记录等实体,且它们之间关联复杂(一对一、一对多、多对多)。事务性要求高(比如确保学生选题和导师确认的原子性)。因此,关系型数据库是更自然的选择。
  • 文档型数据库(如 MongoDB):虽然存储如论文草稿这样的半结构化数据很方便,但对于强一致性要求和复杂关联查询的业务,优势不大。

所以,我们选择 PostgreSQL 作为主数据库,配合 SQLAlchemy ORM 来操作。SQLAlchemy 的强大之处在于其表达能力和灵活性,可以清晰地定义模型关系。

3. 核心模块实现细节与 AI 助攻点

整个系统围绕几个核心模块展开,这里分享几个关键部分的实现,以及 AI 工具如何帮我'偷懒'。

3.1 用户角色与 JWT 鉴权

系统有三类用户:学生、导师、管理员。权限设计是关键。我使用 fastapi-jwt-auth 库来处理 JWT。

首先,定义角色枚举和用户模型:

from enum import Enum
from sqlalchemy import Column, Integer, String, Enum as SQLEnum
from app.database import Base

class UserRole(str, Enum):
    STUDENT = "student"
    TEACHER = 
    ADMIN = 

 ():
    __tablename__ = 
     = Column(Integer, primary_key=, index=)
    username = Column(String(), unique=, index=, nullable=)
    hashed_password = Column(String(), nullable=)
    full_name = Column(String())
    role = Column(SQLEnum(UserRole), nullable=)
    
"teacher"
"admin"
class
User
Base
"users"
id
True
True
50
True
True
False
255
False
100
False
# 其他字段,如邮箱、专业等

接下来是登录和生成 Token 的端点。这里 AI 辅助(如 Copilot)可以快速补全标准的密码验证和 JWT 生成代码:

from datetime import datetime, timedelta
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
from app.auth import create_access_token, authenticate_user
from app.schemas import Token

router = APIRouter(prefix="/auth", tags=["authentication"])

@router.post("/login", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    # AI 辅助:快速写出用户认证逻辑骨架
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="用户名或密码错误")
    
    # 定义 Token 过期时间
    access_token_expires = timedelta(minutes=30)
    
    # 创建 JWT Token,主题(sub)通常放用户 ID
    access_token = create_access_token(
        data={"sub": str(user.id), "role": user.role},
        expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

然后,创建一个依赖项来验证 Token 并获取当前用户。这是权限控制的核心:

from fastapi import Depends, HTTPException, status
from fastapi_jwt_auth import AuthJWT
from app.models import User
from app.database import SessionLocal

def get_current_user(Authorize: AuthJWT = Depends()) -> User:
    try:
        Authorize.jwt_required()
        current_user_id = Authorize.get_jwt_subject()
        current_user_role = Authorize.get_jwt_raw_jwt().get("role")
    except Exception:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="无效或过期的令牌")
    
    db = SessionLocal()
    user = db.query(User).filter(User.id == int(current_user_id)).first()
    db.close()
    
    if user is None:
        raise HTTPException(status_code=404, detail="用户不存在")
    return user

# 更进一步,创建角色检查依赖项
def require_role(required_role: UserRole):
    def role_checker(current_user: User = Depends(get_current_user)):
        if current_user.role != required_role and current_user.role != UserRole.ADMIN:
            # 管理员拥有所有权限
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
        return current_user
    return role_checker

这样,在 API 路径操作函数中,就可以轻松地使用 Depends(require_role(UserRole.TEACHER)) 来保护端点。

3.2 课题双向选择与防重复提交

这是业务逻辑最复杂的部分之一。核心是'课题'模型和'选题记录'模型。

from sqlalchemy import Column, Integer, String, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.database import Base

class TopicStatus(str, Enum):
    PENDING = "pending"      # 待审核(管理员)
    APPROVED = "approved"    # 已发布,可选
    SELECTED = "selected"    # 已被选择
    ARCHIVED = "archived"    # 已归档

class Topic(Base):
    __tablename__ = "topics"
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(200), nullable=False)
    description = Column(Text)
    requirement = Column(Text)
    capacity = Column(Integer, default=1)          # 可容纳学生数
    current_selected = Column(Integer, default=0)
    status = Column(SQLEnum(TopicStatus), default=TopicStatus.PENDING)
    teacher_id = Column(Integer, ForeignKey("users.id"))
    # 关系
    teacher = relationship("User", back_populates="topics_proposed")
    selections = relationship("SelectionRecord", back_populates="topic")

class SelectionRecordStatus(str, Enum):
    APPLIED = "applied"     # 学生已申请
    APPROVED = "approved"   # 导师已同意
    REJECTED = "rejected"   # 导师已拒绝
    FINALIZED = "finalized" # 管理员最终确认

class SelectionRecord(Base):
    __tablename__ = "selection_records"
    id = Column(Integer, primary_key=True, index=True)
    student_id = Column(Integer, ForeignKey("users.id"), nullable=False)
    topic_id = Column(Integer, ForeignKey("topics.id"), nullable=False)
    status = Column(SQLEnum(SelectionRecordStatus), default=SelectionRecordStatus.APPLIED)
    applied_at = Column(DateTime(timezone=True), server_default=func.now())
    processed_at = Column(DateTime(timezone=True))
    # 关系
    student = relationship("User", back_populates="selections_made")
    topic = relationship("Topic", back_populates="selections")

实现学生申请课题的接口时,必须加入防重复提交和容量校验:

@router.post("/topics/{topic_id}/apply", dependencies=[Depends(require_role(UserRole.STUDENT))])
async def apply_for_topic(
    topic_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    # 1. 检查课题是否存在且状态为可选
    topic = db.query(Topic).filter(Topic.id == topic_id, Topic.status == TopicStatus.APPROVED).first()
    if not topic:
        raise HTTPException(status_code=404, detail="课题不存在或不可选")
    
    # 2. 检查是否已申请过该课题(防重复提交)
    existing_application = db.query(SelectionRecord).filter(
        SelectionRecord.student_id == current_user.id,
        SelectionRecord.topic_id == topic_id
    ).first()
    if existing_application:
        raise HTTPException(status_code=400, detail="您已申请过该课题")
    
    # 3. 检查该学生是否已有最终确认的课题(一人一题)
    finalized_selection = db.query(SelectionRecord).filter(
        SelectionRecord.student_id == current_user.id,
        SelectionRecord.status == SelectionRecordStatus.FINALIZED
    ).first()
    if finalized_selection:
        raise HTTPException(status_code=400, detail="您已有最终确认的课题,无法再次申请")
    
    # 4. 检查课题容量(幂等性考虑:高并发下需使用数据库乐观锁或悲观锁)
    if topic.current_selected >= topic.capacity:
        raise HTTPException(status_code=400, detail="该课题名额已满")
    
    # 5. 创建申请记录
    new_application = SelectionRecord(
        student_id=current_user.id,
        topic_id=topic_id,
        status=SelectionRecordStatus.APPLIED
    )
    db.add(new_application)
    # 注意:这里暂时不更新 topic.current_selected,等导师确认后再更新
    db.commit()
    db.refresh(new_application)
    return {"msg": "申请提交成功", "application_id": new_application.id}

导师审批的接口类似,需要更新 SelectionRecord 的状态,并在同意时原子性地更新 Topic 的已选人数。这里 AI 工具在编写数据库事务逻辑、处理各种状态分支时特别有帮助,能减少逻辑遗漏。

4. 安全性分析与性能考量

安全性:

  • XSS 防护:FastAPI 默认对响应进行转义的情况有限,确保前端(如 Vue/React)对从 API 获取的任何动态内容(如课题描述)进行适当的转义或使用文本节点渲染。对于富文本,可以使用白名单过滤库(如 bleach)。
  • CSRF 防护:由于我们采用无状态的 JWT 存储在客户端(如 localStorage 或 Cookie),且 API 设计为 RESTful,对于敏感操作(如 POST、PUT、DELETE)确保使用 HTTPS,并可以考虑为 Cookie 模式的 JWT 设置 SameSite=Strict 属性。
  • 敏感操作日志:所有关键操作(登录、选题、审批、文件上传、成绩录入)都必须记录审计日志。创建一个 AuditLog 模型,记录操作人、时间、IP、动作类型和详情(注意不要记录敏感信息如密码)。
class AuditLog(Base):
    __tablename__ = "audit_logs"
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey("users.id"))
    action = Column(String(100))           # 例如:"TOPIC_APPLY", "GRADE_UPDATE"
    details = Column(Text)                 # 变化详情,JSON 字符串为宜
    ip_address = Column(String(45))
    created_at = Column(DateTime(timezone=True), server_default=func.now())

性能考量:

  • 数据库索引:为所有外键(user_id, topic_id)、经常查询的字段(status, created_at)以及组合查询条件建立索引。例如,查询某个学生的所有申请:INDEX ON selection_records (student_id, status)。
  • 缓存策略:对于一些不常变化但频繁读取的数据,如学院列表、专业列表、已发布的课题总数等,可以使用 Redis 进行缓存。FastAPI 的 fastapi-cache2 等库可以方便地实现接口缓存。
  • 分页查询:列表接口(如课题列表、学生列表)务必支持分页,避免一次性拉取大量数据。

5. 生产环境避坑指南

  • 时区处理:在数据库中使用 TIMESTAMP WITH TIME ZONE 类型(PostgreSQL 的 timestamptz),在代码中统一使用 UTC 时间存储和计算,仅在返回给前端时根据用户所在时区进行转换。
  • 文件上传限制:使用 FastAPI 的 File 和 UploadFile。务必在后台校验文件类型(通过 MIME 类型或文件头)、文件大小,并将文件存储在对象存储(如 MinIO、阿里云 OSS)而非服务器本地,同时重命名文件(使用 UUID)防止冲突和路径遍历攻击。
  • AI 生成代码的审查要点:AI 生成的代码是很好的起点,但必须仔细审查。
    • 业务逻辑正确性:AI 可能不理解你业务的特殊规则(如'导师不能选自己的课题')。
    • 安全性:检查 SQL 注入风险(确保使用 ORM 或参数化查询)、输入验证是否完备、权限检查是否遗漏。
    • 错误处理:AI 生成的代码可能缺乏完善的异常处理和回滚机制。
    • 性能:检查循环内的数据库查询(N+1 问题),建议使用 SQLAlchemy 的 joinedload 或 selectinload 进行优化。

6. 总结与延伸思考

实践证明,AI 辅助开发不仅仅是'自动补全'。它更像一个经验丰富的结对编程伙伴,能帮你快速搭建框架、生成样板代码、提醒你可能的边界情况。这让我从重复劳动中解放出来,更专注于核心业务逻辑和系统设计。

项目基本功能完成后,开始思考如何将 AI 辅助从编码延伸至测试与部署环节:

  • 测试:可以让 AI 根据接口定义自动生成单元测试和集成测试的骨架,甚至模拟各种边界用例数据。例如,为'课题申请'接口生成'已满员申请'、'重复申请'等测试用例。
  • 部署:AI 可以帮忙编写 Dockerfile、docker-compose.yml 以及 CI/CD 流水线脚本(如 GitHub Actions),根据项目结构给出优化建议。
  • 文档:基于代码注释和类型注解,AI 能辅助完善 API 文档描述。

最后,建议不妨动手重构一下自己项目中某个陈旧的模块。用 FastAPI 重写一个简单的 CRUD 接口,全程尝试使用 AI 工具辅助,感受一下它如何理解你的意图、生成结构清晰的代码,以及你如何引导它、审查它、最终与它协作完成一个更健壮、更优雅的实现。这个过程本身,就是对现代开发流程的一次很好的演练。

更多推荐文章

查看全部
  • OpenClaw Agent Skills 核心技能推荐及安装指南
  • 阿里推出 AI 编程插件 Qoder,JetBrains 集成体验一周评测
  • 环形链表检测:哈希表与快慢指针实战
  • 大模型的终局是通用还是专用?
  • 基于 LangChain 从零搭建 AI Agent 实战指南
  • R 语言零截断数据建模实战:复杂数据结构下的统计建模技术
  • 华为 OD 机试:小朋友分组最少调整次数 (Java)
  • PicView 跨平台开源图像浏览器
  • ShellCrash Web面板部署与自定义配置指南
  • 大厂 AI 产品经理招聘数据分析:薪资、门槛与核心技能要求
  • Stable Diffusion WebUI 无障碍改造:键盘导航与屏幕阅读器适配
  • LLama-Factory 结合 HuggingFace 镜像加速模型下载与微调
  • 平台、记忆、评测——AI 竞赛正在换挡
  • Hugging Face 技术主管详解 Llama 3 微调方案
  • ClawX:OpenClaw 可视化桌面客户端入门指南
  • 递归算法找出所有子集的异或总和再求和
  • 红黑树原理及在C++ map与set中的应用
  • 大数据领域 HDFS 在医疗行业的数据存储实践
  • OmniSteward:基于大语言模型的智能语音管家系统
  • 前端现代化:从传统到现代的技术演进

相关免费在线工具

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

目录

  1. 1. 先聊聊痛点:毕业设计管理为啥需要系统化?
  2. 2. 技术选型:为什么是 FastAPI + SQLAlchemy?
  3. 3. 核心模块实现细节与 AI 助攻点
  4. 3.1 用户角色与 JWT 鉴权
  5. 更进一步,创建角色检查依赖项
  6. 3.2 课题双向选择与防重复提交
  7. 4. 安全性分析与性能考量
  8. 5. 生产环境避坑指南
  9. 6. 总结与延伸思考