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 时间存储和计算,仅在返回给前端时根据用户所在时区进行转换。 - 文件上传限制:使用
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 工具辅助,感受一下它如何理解你的意图、生成结构清晰的代码,以及你如何引导它、审查它、最终与它协作完成一个更健壮、更优雅的实现。这个过程本身,就是对现代开发流程的一次很好的演练。