diff --git a/api/cls_mgmt_api.py b/api/cls_mgmt_api.py index 44389be..99fe559 100644 --- a/api/cls_mgmt_api.py +++ b/api/cls_mgmt_api.py @@ -8,7 +8,6 @@ from typing import List from dao.cls_mgmt_dao import ClsMgmtDAO from database import get_db from scheme.cls_mgmt_scheme import ClsMgmtResponse, ClsMgmtCreate -from scheme.users import UserCreate, UserUpdate, UserResponse router = APIRouter() @router.get("/", response_model=List[ClsMgmtResponse]) diff --git a/api/employ_api.py b/api/employ_api.py index d5fa658..1c51b11 100644 --- a/api/employ_api.py +++ b/api/employ_api.py @@ -1,16 +1,14 @@ # api/employ_api.py # 本文件定义 学生就业信息 的所有 API 路由(Controller 层) +# author:王博 from typing import List - from fastapi import APIRouter,HTTPException from fastapi.params import Depends from sqlalchemy.orm import Session - from database import get_db from scheme.employ_scheme import EmployStatusCreate as EModel, EmployStatusResponse, EmployStatusQuery as QModel, \ EmployQueryResponse, EmployStatusDelete as DModel from dao.employ_dao import StudentEmployManageDAO as Emp - # 创建路由器,前缀将在 main.py 中统一添加 router = APIRouter() @@ -90,7 +88,7 @@ async def batch_delete_emp_reco(emp_sta:List[DModel],db:Session = Depends(get_db raise HTTPException(status_code=404, detail="该学生就业记录不存在") # ---------- 批量修改学生就业信息 ---------- -@router.delete('/set_emp_reco/batch/',status_code=204, # response_model=EmployStatusResponse, +@router.put('/set_emp_reco/batch/',status_code=204, # response_model=EmployStatusResponse, responses={404: {"description": "该学生就业记录不存在"}}) async def batch_set_emp_reco(emp_sta:List[EModel],db:Session = Depends(get_db)): """ diff --git a/api/statistics_api.py b/api/statistics_api.py index 6cf9602..118503f 100644 --- a/api/statistics_api.py +++ b/api/statistics_api.py @@ -1,25 +1,18 @@ # api/stats # 本文件定义统计分析相关的所有 API 路由(Controller 层) -from itertools import count -from pydoc import describe - from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from typing import List, Optional -from sqlalchemy import func -from starlette import status from database import get_db from dao.statistics_dao import StatisticsDao -from model.stu_model import StuInfo -from scheme.statistics_scheme import StatsResponse, StuInfoAllExamAboveScore, FailingStudentItem, ClassExamAvgScoreItem, \ +from scheme.statistics_scheme import ClassGenderDistributionItem, StuInfoAllExamAboveScore, FailingStudentItem, ClassExamAvgScoreItem, \ TopSalaryStudentItem, StudentEmpDurationItem, ClassAvgEmpDurationItem, StudentAgeDetailItem router = APIRouter() #### 2.6.1 基本信息动态统计 # - **动态年龄范围查询**:支持用户输入年龄阈值及比较条件(如大于、小于、等于、区间等),动态查询符合条件的学员信息。 -# - **多维度班级统计**:统计每个班级的总人数,以及按性别(男、女)细分的人数分布。 @router.get("/api/stats/students/age_filter", response_model=List[StudentAgeDetailItem], summary="动态年龄范围查询", @@ -47,20 +40,49 @@ def get_students_by_age_condition(db: Session = Depends(get_db), max_age = max_age) return data - # **多维度班级统计**:统计每个班级的总人数,以及按性别(男、女)细分的人数分布。 -@router.get("/class/stats/gender", - response_model=List[StatsResponse], +@router.get("/api/stats/class/gender_distribution", + response_model=List[ClassGenderDistributionItem], summary="班级性别统计", description="统计每个班级的总人数,以及按性别(男、女)细分的人数分布。") def get_class_stats(db: Session = Depends(get_db)): - data = StatisticsDao.get_cls_stats(db) + data = StatisticsDao.get_class_gender_distribution(db) return data + +#### 2.6.2 成绩综合统计: all +@router.get("/class_score", + summary="班级成绩综合统计", + description= """ + 1、查询每次考试成绩都在输入分数线(如80分)以上的学生的编号、姓名和成绩 -- 参数: 分数
+ 2、查询有输入指定次数(如两次)以上不及格的学生的姓名、班级和不及格成绩明细 -- 参数: 不及格次数
+ 3、统计每次考试每个班级的平均分,并支持按分数从高到低或从低到高动态排序 -- 参数: 1:低到高, 2:高到低
+ """) +def get_students_all_exams_above_score( + db: Session = Depends(get_db), + choose: int = Query(..., ge=1, le=3, description="功能选择(1-3): 参考提示"), + player_num: int | float = Query(..., ge=0, le=100, description="输入的具体参数,参考提示") +): + + if choose == 1: + # 查询每次考试成绩都在输入分数线(如80分)以上的学生的编号、姓名和成绩 -- 参数: 分数 + if player_num > 100 or player_num < 0: + raise HTTPException(status_code=400, detail="成绩需要在[0-100]区间") + + data = StatisticsDao.get_stu_info_all_exams_above_score(db, player_num) + elif choose == 2: + # 2、查询有输入指定次数(如两次)以上不及格的学生的姓名、班级和不及格成绩明细 -- 参数: 不及格次数 + data = StatisticsDao.find_students_fail_count(db, player_num) + elif choose == 3: + # 统计每次考试每个班级的平均分,并支持按分数从高到低或从低到高动态排序 -- 参数: 1:低到高, 2:高到低 + data = StatisticsDao.find_class_exam_averages(db, player_num) + + return data + +# 2.6.2 单个接口详细版本 +""" #### 2.6.2 成绩综合统计 # 1- 查询每次考试成绩都在输入分数线(如80分)以上的学生的编号、姓名和成绩。 -# 2- 查询有输入指定次数(如两次)以上不及格的学生的姓名、班级和不及格成绩明细。 -# 3- 统计每次考试每个班级的平均分,并支持按分数从高到低或从低到高动态排序。 @router.get("/api/stats/students/all_exams_above_score", response_model = List[StuInfoAllExamAboveScore], summary="查询成绩在xx分以上的学生信息", @@ -95,13 +117,10 @@ def get_class_exams_avg_scores( ): data = StatisticsDao.find_class_exam_averages(db, order_choose) return data - +""" #### 2.6.3 就业数据统计 - # - 统计就业薪资排名 Top N(动态输入 N)的学生的姓名、班级、就业时间和就业公司。 -# - 统计每个学生的就业时长(计算公式:offer下发时间 - 就业开放时间)。 -# - 统计每个班级的平均就业时长(仅统计进入就业阶段,即有就业开放时间的学生) @router.get("/api/stats/employment/top_salaries", response_model=List[TopSalaryStudentItem], summary="就业薪资top_N", @@ -112,7 +131,6 @@ def get_top_salary_students( data = StatisticsDao.find_top_salary_students(db, top_n) return data - # - 统计每个学生的就业时长(计算公式:offer下发时间 - 就业开放时间)。 @router.get("/api/stats/employment/job_seeking_duration", response_model=List[StudentEmpDurationItem], @@ -122,7 +140,6 @@ def get_students_job_seeking_duration(db: Session = Depends(get_db)): data = StatisticsDao.find_students_employment_duration(db) return data - # - 统计每个班级的平均就业时长(仅统计进入就业阶段,即有就业开放时间的学生) @router.get("/api/stats/class/avg_employment_duration", response_model=List[ClassAvgEmpDurationItem], diff --git a/api/stu_api.py b/api/stu_api.py index a1cfae0..33f079a 100644 --- a/api/stu_api.py +++ b/api/stu_api.py @@ -95,7 +95,7 @@ async def create_student( new_student = StudentDao.create(db, student_dict) except Exception: db.rollback() - raise HTTPException(status_code=400, detail="网络卡顿或有其他人同步上传,请重试") + raise HTTPException(status_code=400, detail="数据异常,请重试") return new_student #============= 修改 ============ @router.put("/{stu_id}", response_model=StudentResponse,summary="通过ID查找修改学生信息") diff --git a/api/stu_score_api.py b/api/stu_score_api.py index a76465f..67135cb 100644 --- a/api/stu_score_api.py +++ b/api/stu_score_api.py @@ -21,7 +21,7 @@ async def create_score( db: Session = Depends(get_db) ): score_level = "优秀" if exam_score >= 90 else "普通" if exam_score >= 60 else "差劲" - warnings="该学生成绩被标记为差劲"if score_level=="差劲" else None + warnings="该学生成绩被标记为差劲, 请及时关注"if score_level=="差劲" else None existing = StuScoreDAO.get_by_id_attempt_all(db, stu_id, exam_attempt) if existing: raise HTTPException(status_code=400, detail="本次考核成绩已存在,请重新确认") diff --git a/api/teacher_api.py b/api/teacher_api.py index b34dc85..b9ad1ec 100644 --- a/api/teacher_api.py +++ b/api/teacher_api.py @@ -5,12 +5,13 @@ from typing import List from database import get_db from dao.teacher_dao import TeacherDao +from scheme.cls_mgmt_scheme import ClsMgmtResponse from scheme.teacher_scheme import TeacherAdd,TeacherUpdate, TeacherResponse router = APIRouter() # 查询所有教师(支持分页) -@router.get("/get_all_teachers", response_model=List[TeacherResponse]) +@router.get("/get_all_teachers", response_model=List[TeacherResponse],summary="查询所有教师") async def get_all_teachers( skip: int = Query(0, ge=0, description="跳过的记录数"), limit: int = Query(100, ge=1, le=200, description="返回的最大记录数"), @@ -21,7 +22,7 @@ async def get_all_teachers( return teachers # FastAPI 自动根据 response_model 转换为 JSON # 根据 ID 和 name 查询单个教师 -@router.get("/get_teacher/{teacher_id_name}", response_model=TeacherResponse) +@router.get("/get_teacher/{teacher_id_name}", response_model=TeacherResponse,summary="查询单个教师") async def get_teacher( teacher_id_name: str = Path(..., description="要查询教师的id或姓名"), db: Session = Depends(get_db) @@ -32,18 +33,23 @@ async def get_teacher( return teacher # 根据 ID 查询教师带教的班级的信息 -@router.get("/get_class_info/{teacher_id}") +@router.get("/get_class_info/{teacher_id}",response_model=ClsMgmtResponse,summary="教师ID查询带教班级信息") async def get_class_info( teacher_id: str = Path(..., description="要查询教师的id"), db: Session = Depends(get_db) ): - teacher = TeacherDao.get_clss_info(db, teacher_id) - if not teacher: - raise HTTPException(status_code=404, detail="用户不存在") - return teacher + # 检查传入的id教师是否存在 + existing = TeacherDao.get_by_id(db, teacher_id) + if not existing: + raise HTTPException(status_code=404, detail="该教师id不存在") + # 查询教师是否有带教班级 + class_info = TeacherDao.get_clss_info(db, teacher_id) + if not class_info: + raise HTTPException(status_code=404, detail="无带教班级") + return class_info # 添加教师信息 -@router.post("/add_teacher", response_model=TeacherResponse) +@router.post("/add_teacher", response_model=TeacherResponse,summary="增加教师信息") async def add_teacher( teacher_data: TeacherAdd, db: Session = Depends(get_db) @@ -53,7 +59,7 @@ async def add_teacher( return new_teacher # 根据教师id更新教师信息 -@router.put("/update_teacher/{teacher_id}", response_model=TeacherResponse) +@router.put("/update_teacher/{teacher_id}", response_model=TeacherResponse,summary="更新教师信息") async def update_teacher( teacher_id: str, teacher_data: TeacherUpdate, @@ -62,14 +68,14 @@ async def update_teacher( # 检查传入的id教师是否存在 existing = TeacherDao.get_by_id(db, teacher_id) if not existing: - raise HTTPException(status_code=404, detail="用户不存在") + raise HTTPException(status_code=404, detail="该教师不存在") # 执行更新 updated = TeacherDao.update(db, teacher_id, teacher_data) return updated # 删除教师 -@router.delete("/delete_teacher/{teacher_id}") +@router.delete("/delete_teacher/{teacher_id}",summary="删除教师") async def delete_teacher( teacher_id: str, db: Session = Depends(get_db) @@ -77,6 +83,6 @@ async def delete_teacher( success = TeacherDao.delete(db, teacher_id) if not success: - raise HTTPException(status_code=404, detail="用户不存在") + raise HTTPException(status_code=404, detail="教师不存在") return "该教师已删除" \ No newline at end of file diff --git a/dao/cls_mgmt_dao.py b/dao/cls_mgmt_dao.py index 01fc5ff..d9f2777 100644 --- a/dao/cls_mgmt_dao.py +++ b/dao/cls_mgmt_dao.py @@ -1,6 +1,6 @@ # dao/cls_mgmt_dao.py # 本文件封装对 ClsMgmt 表的所有数据库操作(增、删、改、查) - +from fastapi import HTTPException from sqlalchemy.orm import Session from model.cls_mgmt_model import ClsMgmt @@ -19,7 +19,7 @@ class ClsMgmtDAO: :param limit: 最大返回条数 :return: 用户对象列表 """ - return db.query(ClsMgmt).offset(skip).limit(limit).all() + return db.query(ClsMgmt).filter(ClsMgmt.is_deleted == 0).offset(skip).limit(limit).all() # -> Optional[ClsMgmt] # @staticmethod @@ -36,7 +36,7 @@ class ClsMgmtDAO: 根据主键 ID 获取单个班级 :return: 班级对象或 None """ - return db.query(ClsMgmt).filter(ClsMgmt.id == class_id).first() + return db.query(ClsMgmt).filter(ClsMgmt.id == class_id,ClsMgmt.is_deleted == 0).first() @@ -115,7 +115,8 @@ class ClsMgmtDAO: update_cls.lecturer_id=cls_data.lecturer_id db.commit() return update_cls - return f"班级{class_id}不存在" + + raise HTTPException(status_code=404, detail=f"班级{class_id}不存在") diff --git a/dao/employ_dao.py b/dao/employ_dao.py index 8f4e043..4af7465 100644 --- a/dao/employ_dao.py +++ b/dao/employ_dao.py @@ -7,8 +7,7 @@ from sqlalchemy.orm import Session from model.employ_model import StudentEmployManage as ETable from scheme.employ_scheme import EmployStatusCreate as EModel, EmployStatusQuery as QModel,EmployStatusDelete as DModel from model.stu_model import StuInfo - -from typing import Optional, List +from typing import List class StudentEmployManageDAO: """ 封装stu_emp_mgmt 表的所有数据库操作(增、删、改、查) @@ -197,13 +196,13 @@ class StudentEmployManageDAO: :return: 返回查询到的 所有 学生就业信息 """ e_all = db.query(ETable) - if emp_sta_query.stu_id: # 根据 学生编号 筛选出 学生就业信息 + if emp_sta_query.stu_id is not None: # 根据 学生编号 筛选出 学生就业信息 e_all = e_all.filter(ETable.stu_id == emp_sta_query.stu_id) - if emp_sta_query.emp_company: # 根据 就业公司名称 模糊筛选出 学生就业信息 + if emp_sta_query.emp_company is not None: # 根据 就业公司名称 模糊筛选出 学生就业信息 e_all = e_all.filter(ETable.emp_company.like(f"%{emp_sta_query.emp_company}%")) - if emp_sta_query.min_salary: # 根据 最小薪资范围 筛选出 大于 最小薪资范围的 学生就业信息 + if emp_sta_query.min_salary is not None: # 根据 最小薪资范围 筛选出 大于 最小薪资范围的 学生就业信息 e_all = e_all.filter(ETable.salary >= emp_sta_query.min_salary) - if emp_sta_query.max_salary: # 根据 最大薪资范围 筛选出 小于 最大薪资范围的 学生就业信息 + if emp_sta_query.max_salary is not None: # 根据 最大薪资范围 筛选出 小于 最大薪资范围的 学生就业信息 e_all = e_all.filter(ETable.salary <= emp_sta_query.max_salary) total = e_all.count() diff --git a/dao/statistics_dao.py b/dao/statistics_dao.py index 6453fe3..974444a 100644 --- a/dao/statistics_dao.py +++ b/dao/statistics_dao.py @@ -2,6 +2,8 @@ # 本文件封装对 所有相关表的 所有数据库操作(统计查询) from collections import defaultdict +from fastapi.openapi.utils import status_code_ranges +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session, joinedload from sqlalchemy import func, case, and_, join, asc, desc @@ -9,7 +11,7 @@ from model import StudentEmployManage from model.stu_model import StuInfo from model.stu_score_model import StuScore from typing import Optional, List, Dict -from scheme.statistics_scheme import StatsResponse, ExamScoreItem, StuInfoAllExamAboveScore, FailingStudentItem, \ +from scheme.statistics_scheme import ClassGenderDistributionItem, ExamScoreItem, StuInfoAllExamAboveScore, FailingStudentItem, \ ClassExamAvgScoreItem, TopSalaryStudentItem, StudentEmpDurationItem, ClassAvgEmpDurationItem, StudentAgeDetailItem @@ -17,7 +19,7 @@ class StatisticsDao: """用户数据访问对象,所有方法均为静态方法,方便调用""" @staticmethod - def get_cls_stats(db: Session) -> List[StatsResponse]: + def get_class_gender_distribution(db: Session) -> List[ClassGenderDistributionItem]: all_cls_gender_stats = ( db.query( @@ -31,7 +33,7 @@ class StatisticsDao: ) return [ - StatsResponse( + ClassGenderDistributionItem( cls_id=i.cls_id, total_cnt=i.total_cnt, man_cnt=i.man_cnt, @@ -96,6 +98,10 @@ class StatisticsDao: @staticmethod def find_students_fail_count(db: Session, fail_cnt: int) -> List[FailingStudentItem]: + test_max_cnt = len(db.query(StuScore.exam_attempt).group_by(StuScore.exam_attempt).all()) + if fail_cnt > test_max_cnt: + raise HTTPException(status_code = 400, detail = f'最大次数为{test_max_cnt}') + stu_fail_dict = StatisticsDao.get_stu_dict_who_fail(db) ans = [] diff --git a/dao/teacher_dao.py b/dao/teacher_dao.py index 5542684..32a1cbd 100644 --- a/dao/teacher_dao.py +++ b/dao/teacher_dao.py @@ -61,9 +61,9 @@ class TeacherDao: :return: 创建后的 teacher 对象(含id ) """ - # 教师id自动生成 格式:T+随机四位数 并进行重复验证 + # 教师id自动生成 格式:t+随机三位数 并进行重复验证 while True: - new_id = f"T{random.randint(100, 999)}" # 确保是4位数,不会出现 0012 这种情况 + new_id = f"t{random.randint(100, 999)}" # 确保是3位数,不会出现 002 这种情况 # 查询数据库是否已存在这个 id exists = db.query(Teacher).filter(Teacher.id == new_id).first() diff --git a/model/__init__.py b/model/__init__.py index 8e46cdc..d6d358d 100644 --- a/model/__init__.py +++ b/model/__init__.py @@ -9,7 +9,6 @@ from model.employ_model import StudentEmployManage from model.stu_model import StuInfo from model.stu_score_model import StuScore from model.teacher_model import Teacher -from model.users import User __all__ = [ "Base", @@ -19,5 +18,4 @@ __all__ = [ "StuInfo", "StuScore", "Teacher", - "User", ] \ No newline at end of file diff --git a/scheme/employ_scheme.py b/scheme/employ_scheme.py index 4d30ff5..24c16b2 100644 --- a/scheme/employ_scheme.py +++ b/scheme/employ_scheme.py @@ -1,8 +1,9 @@ # scheme/employ.py +# 本文件定义 学生就业信息 接口层所用到的 请求体、响应体 +# author:王博 from datetime import date from decimal import Decimal from typing import List - from pydantic import Field, model_validator, BaseModel # ---------- -------------------------------请求模型 ------------------------------------------------ diff --git a/scheme/statistics_scheme.py b/scheme/statistics_scheme.py index 80aaf83..88315e2 100644 --- a/scheme/statistics_scheme.py +++ b/scheme/statistics_scheme.py @@ -5,15 +5,10 @@ from typing import Optional, List # ---------- 请求模型 ---------- -class StatsScoreRequest(BaseModel): - choose: int = Field(..., ge=0, le=3) - - email: EmailStr - full_name: Optional[str] = Field(None, max_length=100) # ---------- 响应模型 ---------- # 多维度班级统计 -class StatsResponse(BaseModel): +class ClassGenderDistributionItem(BaseModel): cls_id: str total_cnt: int man_cnt: int diff --git a/scheme/stu_scheme.py b/scheme/stu_scheme.py index a04bdd2..44904e2 100644 --- a/scheme/stu_scheme.py +++ b/scheme/stu_scheme.py @@ -9,7 +9,7 @@ class StudentCreate(BaseModel): name: str = Field(..., min_length=1, max_length=20, description="姓名") gender: str = Field(..., min_length=1, max_length=1, description="性别") age: int = Field(..., ge=1, le=100, description="年龄") - hometown: Optional[str] = Field(None, min_length=3, max_length=50, description="籍贯") + hometown: Optional[str] = Field(None, min_length=1, max_length=50, description="籍贯") grad_school: str = Field(..., min_length=1, max_length=50, description="毕业院校") major: str = Field(..., min_length=1, max_length=50, description="专业名称") education: str = Field(..., min_length=1, max_length=20, description="学历")