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

最近在帮学校实验室做一个毕业设计管理系统,从零开始用 Python 实现。整个过程最大的感受是,合理利用 AI 辅助工具(比如 Cursor、GitHub Copilot)真的能极大提升开发效率,尤其是处理那些重复性的 CRUD 代码和复杂的业务逻辑校验时。今天就来分享一下我的实战经验,聊聊怎么用 Python 技术栈,在 AI 的帮助下,构建一个靠谱的毕业设计管理系统。

毕业设计管理

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 = "teacher" ADMIN = "admin" class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String(50), unique=True, index=True, nullable=False) hashed_password = Column(String(255), nullable=False) full_name = Column(String(100)) role = Column(SQLEnum(UserRole), nullable=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 时间存储和计算,仅在返回给前端时根据用户所在时区进行转换。
  • 文件上传限制:使用 FastAPIFileUploadFile。务必在后台校验文件类型(通过 MIME 类型或文件头)、文件大小,并将文件存储在对象存储(如 MinIO、阿里云 OSS)而非服务器本地,同时重命名文件(使用 UUID)防止冲突和路径遍历攻击。
  • AI 生成代码的审查要点:AI 生成的代码是很好的起点,但必须仔细审查。
    • 业务逻辑正确性:AI 可能不理解你业务的特殊规则(如“导师不能选自己的课题”)。
    • 安全性:检查 SQL 注入风险(确保使用 ORM 或参数化查询)、输入验证是否完备、权限检查是否遗漏。
    • 错误处理:AI 生成的代码可能缺乏完善的异常处理和回滚机制。
    • 性能:检查循环内的数据库查询(N+1 问题),建议使用 SQLAlchemy 的 joinedloadselectinload 进行优化。

6. 总结与延伸思考

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

开发协作

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

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

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

Read more

更底一层——聊聊python的执行与环境

本文目录 * python 代码如何被 CPU 执行 * python代码执行的起点 * .dll 的作用与python解释器的定义 * python 解释器所在的文件结构 * 不同类型的python解释器 * python 的依赖与环境 * 关于模块,包,编译拓展的概念 * sys.path * import机制 * 拓展的具体形态 * 字节码与dll调用原理 * `if __name__ == "__main__"` * 虚拟环境的原理 写在前面 一段 python 代码如何变为机器码?python 解释器,依赖,包,虚拟环境,他们都在做什么?from A import B 的作用竟然只是编码时可以少打点字任何 .pyd 和 .dll,其实都是 CALL 指令的对象,真正重要的只有 python3xx.

By Ne0inhk
Python 打包编译工具Pyinstaller 与 Nuitka 特性对比

Python 打包编译工具Pyinstaller 与 Nuitka 特性对比

文章目录 * 概要 * 简介 * Pyinstaller * Nuitka * Python项目 * 特性剖析 * 相同点 * 差异点 * 场景选型 * 总结 概要 当我们发布Python项目时,有时为了隔离运行环境的差异 或者 不希望以源码的形式发布,一种常用的方法是将Python项目打包/编译成二进制文件(更具体的讨论在Python项目加密 中),打包编译的工具有很多,其中PyInstaller 和 Nuitka 是目前最主流的两个工具。它们都能将 .py 文件打包成无需安装 Python 环境即可运行的二进制程序,但其底层机制、性能表现和适用场景存在显著差异。 结合实际的项目实践,本文从功能原理、特性异同、性能对比及实际选型建议等 详细对比 PyInstaller 与 Nuitka 的核心特性,供有需要的伙伴参考以项目需求做出更合理的技术选型。 简介 Pyinstaller PyInstaller 是一个流行且成熟的 Python 打包工具,能Python 脚本及其依赖项(

By Ne0inhk

Python系列:打造高效调试利器——彩色日志全攻略

1. 为什么我们需要彩色日志 调试代码时,你是否经常被满屏单调的黑白日志搞得头晕眼花?我曾经接手过一个遗留项目,每次排查问题都要在密密麻麻的日志海洋里寻找关键错误信息,那种体验简直让人崩溃。后来我发现,给日志添加颜色标记可以大幅提升调试效率 - 重要错误一眼就能看到红色警示,调试信息用淡色显示,关键流程用醒目颜色标注。 Python的标准logging模块虽然功能强大,但默认输出确实太过朴素。想象一下,当你的服务突然报错时,在一堆灰色文本中快速定位到那个鲜红的ERROR信息,能节省多少排查时间?这就是coloredlogs库的价值所在 - 它让日志阅读从"找茬游戏"变成了"一目了然"的体验。 在实际项目中,我发现彩色日志特别适合以下场景: * 开发调试时快速区分不同级别的日志 * 生产环境监控时突出显示关键错误 * 复杂流程中标记不同模块的输出 * 教学演示时让代码执行过程更直观 2. 快速上手coloredlogs 2.1 安装与基础配置 安装coloredlogs简单到只需一行命令: pip install coloredlogs 基础配置也极其简单,

By Ne0inhk
华为od 面试八股文_Python_05_含答案

华为od 面试八股文_Python_05_含答案

目录 1:dict和set的区别? 2:Python的实现和其他语言不一致? 3:你能想到其他语言为什么还要保留红黑树的实现呢?不都直接用hashTable? 1.有序性 (Ordering):红黑树最核心、最无可替代的优势 2.稳定的性能与可预测性 (Stable Performance & Predictability) 3.对键(Key)的要求更宽松 4.语言设计哲学的差异 4:在Python中,进程和线程的区别? 5:Python数据处理的库有哪些?用过吗? 7:Python的迭代器(Iterator)和生成器(Generator)有什么区别? 8:什么是协程?它和线程有什么本质区别? 9:Numpy和Pandas 区别在哪? 优先选择 NumPy 的场景: 优先选择 Pandas 的场景: 1:

By Ne0inhk