第四版_完整
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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查找修改学生信息")
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
|
||||
# ---------- -------------------------------请求模型 ------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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="学历")
|
||||
|
||||
Reference in New Issue
Block a user