第四版_完整

This commit is contained in:
jianqi
2026-09-14 15:23:26 +08:00
parent 06c329fdd1
commit c2909191d2
14 changed files with 84 additions and 64 deletions
-1
View File
@@ -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])
+2 -4
View File
@@ -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)):
"""
+37 -20
View File
@@ -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分)以上的学生的编号、姓名和成绩 -- 参数: 分数<br>
2、查询有输入指定次数(如两次)以上不及格的学生的姓名、班级和不及格成绩明细 -- 参数: 不及格次数<br>
3、统计每次考试每个班级的平均分,并支持按分数从高到低或从低到高动态排序 -- 参数: 1:低到高, 2:高到低<br>
""")
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],
+1 -1
View File
@@ -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查找修改学生信息")
+1 -1
View File
@@ -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="本次考核成绩已存在,请重新确认")
+18 -12
View File
@@ -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 "该教师已删除"
+5 -4
View File
@@ -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}不存在")
+5 -6
View File
@@ -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()
+9 -3
View File
@@ -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 = []
+2 -2
View File
@@ -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()
-2
View File
@@ -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",
]
+2 -1
View File
@@ -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
# ---------- -------------------------------请求模型 ------------------------------------------------
+1 -6
View File
@@ -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
+1 -1
View File
@@ -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="学历")