diff --git a/advisor_api.py b/advisor_api.py new file mode 100644 index 0000000..9f89e10 --- /dev/null +++ b/advisor_api.py @@ -0,0 +1,68 @@ +# ============================================================ +# api/advisor_api.py —— 顾问模块路由层(Controller) +# 职责:定义顾问相关的所有 HTTP 接口,调用 dao 层完成业务处理 +# 统一前缀:/advisor(在 main.py 中挂载) +# ============================================================ + +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.orm import Session +from dao import advisor_dao # 顾问数据访问层 +from database import get_db # 数据库会话依赖 +from scheme.advisor_scheme import ( + AdvisorCreate, # 新增顾问请求模型 + AdvisorResp, # 顾问响应模型 + AdvisorStudentResp, # 顾问名下学生响应模型 + AdvisorUpdate, # 更新顾问请求模型 +) + +# router = APIRouter(prefix="/advisor", tags=["顾问管理模块"]) +router = APIRouter() # 前缀在 main.py 统一挂载为 /advisor + +# 新增顾问(POST /advisor/) +@router.post("/", response_model=AdvisorResp, summary="新增顾问") +def add_advisor(advisor: AdvisorCreate, db: Session = Depends(get_db)): + # 校验顾问编号唯一性:已存在则拒绝新增 + if advisor_dao.get_advisor_by_no(db, advisor.advisor_no): + raise HTTPException(status_code=400, detail=f"顾问编号 {advisor.advisor_no} 已存在") + return advisor_dao.create_advisor(db, advisor) + +# 顾问列表/搜索(GET /advisor/):可按编号或姓名模糊搜索 +@router.get("/", response_model=list[AdvisorResp], summary="顾问列表/搜索") +def get_advisor_list( + search_keyword: str | None = Query(default=None, description="按顾问编号或姓名模糊搜索"), + db: Session = Depends(get_db), +): + return advisor_dao.get_advisor_list(db, search_keyword) + +# 按编号查询单个顾问(GET /advisor/{advisor_no}) +@router.get("/{advisor_no}", response_model=AdvisorResp, summary="按编号查询顾问") +def get_advisor(advisor_no: str, db: Session = Depends(get_db)): + advisor = advisor_dao.get_advisor_by_no(db, advisor_no) + if not advisor: + raise HTTPException(status_code=404, detail=f"顾问 {advisor_no} 不存在") + return advisor + +# 查询该顾问名下所有学生(GET /advisor/{advisor_no}/students) +@router.get("/{advisor_no}/students", response_model=list[AdvisorStudentResp], summary="查询顾问名下学生") +def get_advisor_students(advisor_no: str, db: Session = Depends(get_db)): + # 先确认顾问存在 + if not advisor_dao.get_advisor_by_no(db, advisor_no): + raise HTTPException(status_code=404, detail=f"顾问 {advisor_no} 不存在") + return advisor_dao.get_students_by_advisor_no(db, advisor_no) + +# 更新顾问信息(PUT /advisor/{advisor_no}) +@router.put("/{advisor_no}", response_model=AdvisorResp, summary="更新顾问") +def update_advisor(advisor_no: str, update: AdvisorUpdate, db: Session = Depends(get_db)): + # model_dump(exclude_unset=True):只传被修改的字段(没传的不覆盖) + advisor = advisor_dao.update_advisor(db, advisor_no, update.model_dump(exclude_unset=True)) + if not advisor: + raise HTTPException(status_code=404, detail=f"顾问 {advisor_no} 不存在") + return advisor + +# 删除顾问(DELETE /advisor/{advisor_no},逻辑删除) +@router.delete("/{advisor_no}", response_model=AdvisorResp, summary="删除顾问") +def delete_advisor(advisor_no: str, db: Session = Depends(get_db)): + advisor = advisor_dao.delete_advisor(db, advisor_no) + if not advisor: + raise HTTPException(status_code=404, detail=f"顾问 {advisor_no} 不存在") + return advisor diff --git a/api_analysis.py b/api_analysis.py new file mode 100644 index 0000000..527c4be --- /dev/null +++ b/api_analysis.py @@ -0,0 +1,99 @@ +# ============================================================ +# api/api_analysis.py —— 统计分析模块路由层(Controller) +# 职责:定义各类统计查询接口,调用 dao 层的统计分析函数 +# 统一前缀:/analysis(在 main.py 中挂载) +# ============================================================ + +from fastapi import APIRouter, Query, Depends +from sqlalchemy.orm import Session + +# 导入 dao 层的统计分析函数 +from dao.age_analysis import ( + analysis_student_age, # 年龄区间统计 + analysis_class_gender, # 班级性别分布 + analysis_score_over, # 达标学生(每次考试都在分数线以上) + analysis_score_not_qualified, # 不及格次数达阈值学生 + analysis_avg_score_order, # 每次考试班级平均分 + analysis_employment_salary_order, # 就业薪资 Top N + analysis_student_employment_time, # 学生就业时长 + avg_employment_time, # 班级平均就业时长 +) +from database import get_db # 数据库会话依赖 +# 导入统计接口的响应模型 +from scheme.schema_analysis import ( + StudentOut, + StudentGenderOut, + StudentRecordOut1, + StudentRecordOut2, + StudentRecordOut3, + StudentRecordOut4, + StudentRecordOut5, + StudentRecordOut6, +) + +router = APIRouter() + + +# 动态年龄范围查询(GET /analysis/analysis_age): +# 支持输入年龄阈值(大于等于/小于等于)动态查询学员信息 +@router.get("/analysis_age", response_model=list[StudentOut]) +async def analysis_age( + db: Session = Depends(get_db), + min_age: int | None = Query(None, ge=0, description="请输入查询年龄大于等于"), + max_age: int | None = Query(None, ge=0, description="请输入查询年龄小于等于"), +): + return analysis_student_age(db, min_age, max_age) + + +# 统计每个班级的总人数,以及按性别(男、女)细分的人数分布(GET /analysis/analysis_gender) +@router.get("/analysis_gender", response_model=list[StudentGenderOut]) +async def analysis_gender(db: Session = Depends(get_db)): + return analysis_class_gender(db) + + +# 查询每次考试成绩都在输入分数线(如80分)以上的学生的编号、姓名和成绩(GET /analysis/analysis_scores) +@router.get("/analysis_scores", response_model=list[StudentRecordOut1]) +async def analysis_scores( + db: Session = Depends(get_db), + score: float = Query(0, ge=0, le=100, description="请输入查询学生每次考试分数在多少以上"), +): + return analysis_score_over(db, score) + + +# 查询有输入指定次数(如两次)以上不及格的学生的姓名、班级和不及格成绩明细(GET /analysis/analysis_not_qualified) +@router.get("/analysis_not_qualified", response_model=list[StudentRecordOut2]) +async def analysis_not_qualified( + db: Session = Depends(get_db), + n: int = Query(1, ge=1, description="请输入不及格次数下限"), +): + return analysis_score_not_qualified(db, n) + + +# 统计每次考试每个班级的平均分,支持按分数从高到低或从低到高动态排序(GET /analysis/analysis_avg_score) +@router.get("/analysis_avg_score", response_model=list[StudentRecordOut3]) +async def analysis_avg_score( + db: Session = Depends(get_db), + sort: str = Query("asc", description="请输入排序类型 asc/desc"), +): + return analysis_avg_score_order(db, sort) + + +# 统计就业薪资排名 Top N(动态输入 N)的学生的姓名、班级、就业时间和就业公司(GET /analysis/analysis_salary_top) +@router.get("/analysis_salary_top", response_model=list[StudentRecordOut4]) +async def analysis_salary_top( + db: Session = Depends(get_db), + n: int = Query(1, ge=1, description="请输入取前几名薪资"), +): + return analysis_employment_salary_order(db, n) + + +# 统计每个学生的就业时长(offer下发时间 - 就业开放时间,单位秒)(GET /analysis/analysis_employment_time) +@router.get("/analysis_employment_time", response_model=list[StudentRecordOut5]) +async def analysis_employment_time(db: Session = Depends(get_db)): + return analysis_student_employment_time(db) + + +# 统计每个班级的平均就业时长(仅统计进入就业阶段,即有就业开放时间的学生)(GET /analysis/analysis_avg_employment_time) +@router.get("/analysis_avg_employment_time", response_model=list[StudentRecordOut6]) +async def analysis_avg_employment_time(db: Session = Depends(get_db)): + return avg_employment_time(db) diff --git a/classinfo_api.py b/classinfo_api.py new file mode 100644 index 0000000..6d479b9 --- /dev/null +++ b/classinfo_api.py @@ -0,0 +1,74 @@ +# ============================================================ +# api/classinfo_api.py —— 班级模块路由层(Controller) +# 职责:定义班级相关的所有 HTTP 接口,调用 dao 层完成业务处理 +# 统一前缀:/classinfo(在 main.py 中挂载) +# ============================================================ + +# 路由接口 +from typing import List + +from fastapi import APIRouter,Depends,HTTPException +from database import get_db # 数据库会话依赖 +from dao.classinfo_dao import create_class, get_class, get_all_class, update_class, delete_class, \ + get_class_all_teachers, get_class_all_students # 班级数据访问层函数 +from scheme.classinfo_scheme import ClassCreate,ClassUpdate,ClassIn # 班级请求/响应模型 + + +router=APIRouter() + +# 新增班级(POST /classinfo/) +@router.post("/",response_model=ClassIn) +def add_class(class_data:ClassCreate,db=Depends(get_db)): + return create_class(db=db,obj=class_data) + +# 条件查询班级(GET /classinfo/search):可按 id 或名称筛选 +@router.get("/search",response_model=List[ClassIn]) +def get_cls(class_id:int=None,class_name:str=None,db=Depends(get_db)): + res_list=get_class(db=db,class_id=class_id,class_name=class_name) + # 如果指定class_id但是查不到,才抛出404 + if class_id is not None and len(res_list)==0: + raise HTTPException(status_code=404, detail="班级不存在") + return res_list + +# 查询全部班级(GET /classinfo/) +@router.get("/",response_model=List[ClassIn]) +def get_all(db=Depends(get_db)): + return get_all_class(db=db) + +# 修改班级(PUT /classinfo/{class_id}) +@router.put("/{class_id}",response_model=ClassIn) +def up_class(class_id:int,class_data:ClassUpdate,db=Depends(get_db)): + res=update_class(db=db,class_id=class_id, obj=class_data) + if not res: + raise HTTPException(status_code=404, detail="班级不存在") + return res + +# 逻辑删除班级(DELETE /classinfo/{class_id}) +@router.delete("/{class_id}") +def del_class(class_id:int,db=Depends(get_db)): + res=delete_class(db=db,class_id=class_id) + if not res: + raise HTTPException(status_code=404,detail="班级不存在") + return {"msg": "逻辑删除成功"} + +# 1. 查询班级下全部老师(GET /classinfo/{class_id}/teachers) +@router.get("/{class_id}/teachers") +def get_class_teacher_list(class_id:int, db=Depends(get_db)): + teacher_list = get_class_all_teachers(db, class_id) + if teacher_list is None: + raise HTTPException(status_code=404, detail="班级不存在") + return { + "class_id": class_id, + "teacher_list": teacher_list + } + +# 2. 查询班级下全部学生(GET /classinfo/{class_id}/students) +@router.get("/{class_id}/students") +def get_class_student_list(class_id:int, db=Depends(get_db)): + student_list = get_class_all_students(db, class_id) + if student_list is None: + raise HTTPException(status_code=404, detail="班级不存在") + return { + "class_id": class_id, + "student_list": student_list + } diff --git a/teacher.py b/teacher.py new file mode 100644 index 0000000..898d492 --- /dev/null +++ b/teacher.py @@ -0,0 +1,17 @@ +from sqlalchemy import Column, Integer, String, ForeignKey,Date,DateTime +from sqlalchemy.orm import relationship +from sqlalchemy.sql import func +from model import Base +from model.teacher_class import teacher_class + + +class Teacher(Base): + __tablename__="teacher" + id=Column(Integer,primary_key=True,autoincrement=True) + teacher_no=Column(String(50),unique=True,nullable=False) + name=Column(String(50),nullable=False) + phone=Column(String(50),nullable=False) + is_deleted=Column(Integer,default=0) + created_at=Column(DateTime,server_default=func.now()) + updated_at=Column(DateTime,onupdate=func.now()) + classinfo = relationship("ClassInfo",secondary=teacher_class, back_populates="teachers") \ No newline at end of file diff --git a/teacher_class.py b/teacher_class.py new file mode 100644 index 0000000..3c7e171 --- /dev/null +++ b/teacher_class.py @@ -0,0 +1,12 @@ +from sqlalchemy import create_engine, Column, Integer, String, Float, ForeignKey, Table, Date +from sqlalchemy.orm import relationship +from model import Base + +teacher_class = Table( + "teacher_class", # 中间表的名字 + Base.metadata, # 绑定到 Base 的元数据 + Column("class_id", Integer, ForeignKey("classinfo.id"), primary_key=True), + Column("teacher_id", Integer, ForeignKey("teacher.id"), primary_key=True) , + Column("role",String(50)) + +) \ No newline at end of file