统计分析模块
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from database import get_db
|
||||
from dao.statistics_dao import StatisticsDao
|
||||
from schema.statistics_schema import ResponseModel,ResponseModel1,ResponseModel2,ResponseModel3,ResponseModel4,ResponseModel5,ResponseModel6
|
||||
from math import ceil
|
||||
|
||||
BasicInformationAPI = APIRouter(tags=['统计分析'])
|
||||
|
||||
@BasicInformationAPI.get('/basic-information',summary='查询年龄区间内的学员信息')
|
||||
def get_students_by_age_range_api(
|
||||
n:int=Query(..., description='页码,从1开始', ge=1),
|
||||
m:int=Query(..., description='每页条数', ge=1),
|
||||
min_age: int = Query(..., description='最小年龄', ge=0),
|
||||
max_age: int = Query(..., description='最大年龄', le=100),
|
||||
db = Depends(get_db)):
|
||||
if min_age > max_age:
|
||||
return {f'code=400,message=最小年龄不能大于最大年龄'}
|
||||
|
||||
req,total = StatisticsDao.get_students_by_age_range_dao(n,m,min_age, max_age,db)
|
||||
|
||||
return ResponseModel(code=200,
|
||||
message='查询成功',
|
||||
total=total,
|
||||
total_pages=ceil(total/m) if total > 0 else 0,
|
||||
data=req)
|
||||
|
||||
@BasicInformationAPI.get('/basic-information/{class_id}',summary='统计每个班级的⼈数以及男⽣/⼥⽣的⼈数')
|
||||
def get_students_by_class_id_api(class_id:str,
|
||||
db = Depends(get_db)):
|
||||
total_count , male_count , female_count = StatisticsDao.get_students_by_class_id_dao(class_id,db)
|
||||
return {f'code:200,message=查询成功, 全班总人数:{total_count}, 班级男生人数:{male_count}, 班级女生人数:{female_count}'}
|
||||
|
||||
|
||||
@BasicInformationAPI.get('/scores',summary='在某个分数段的学生')
|
||||
def get_students_by_score_api(
|
||||
n:int=Query(..., description='页码,从1开始', ge=1),
|
||||
m:int=Query(..., description='每页条数', ge=1),
|
||||
score:float=Query(ge=0,le=100),
|
||||
db = Depends(get_db)):
|
||||
if score < 0 or score > 100:
|
||||
return {f'code=400,message=分数需要在0~100!'}
|
||||
req,total = StatisticsDao.get_students_by_score_dao(n,m,score,db)
|
||||
return ResponseModel1(code=200,
|
||||
message='查询成功',
|
||||
total=total,
|
||||
total_pages=ceil(total/m) if total > 0 else 0,
|
||||
data=req)
|
||||
|
||||
@BasicInformationAPI.get('/scores_no_pass',summary='查询有n门成绩不合格的学生')
|
||||
def get_students_by_no_pass_api(
|
||||
n:int=Query(..., description='页码,从1开始', ge=1),
|
||||
m:int=Query(..., description='每页条数', ge=1),
|
||||
fail_count:int=Query(..., description='不及格门数', ge=1),
|
||||
db = Depends(get_db)):
|
||||
req,total = StatisticsDao.get_student_by_no_pass_dao(n,m,fail_count,db)
|
||||
return ResponseModel2(code=200,
|
||||
message='查询成功',
|
||||
total=total,
|
||||
total_pages=ceil(total/m) if total > 0 else 0,
|
||||
data=req)
|
||||
|
||||
@BasicInformationAPI.get('/scores_avg', summary='统计每次考试每个班级的平均分(从高到低排序)')
|
||||
def get_class_exam_avg_api(
|
||||
n:int=Query(..., description='页码,从1开始', ge=1),
|
||||
m:int=Query(..., description='每页条数', ge=1),
|
||||
db = Depends(get_db)):
|
||||
req,total = StatisticsDao.get_class_exam_avg_score_dao(n,m,db)
|
||||
return ResponseModel3(code=200,
|
||||
message='查询成功',
|
||||
total=total,
|
||||
total_pages=ceil(total/m) if total > 0 else 0,
|
||||
data=req)
|
||||
|
||||
@BasicInformationAPI.get('/salary_top', summary='查询就业薪资最高的前五名学生')
|
||||
def get_salary_top_api(
|
||||
m:int=Query(5, description='查询前m名', ge=1),
|
||||
db = Depends(get_db)):
|
||||
req = StatisticsDao.get_salary_top_dao(m,db)
|
||||
return ResponseModel4(code=200,
|
||||
message='查询成功',
|
||||
data=req)
|
||||
|
||||
@BasicInformationAPI.get('/time_size', summary='统计每个学生的就业时长(offer下发时间-就业开放时间)')
|
||||
def get_time_size_api(
|
||||
n:int=Query(..., description='页码,从1开始', ge=1),
|
||||
m:int=Query(..., description='每页条数', ge=1),
|
||||
db = Depends(get_db)):
|
||||
req,total = StatisticsDao.get_time_size_dao(n,m,db)
|
||||
return ResponseModel5(code=200,
|
||||
message='查询成功',
|
||||
total=total,
|
||||
total_pages=ceil(total/m) if total > 0 else 0,
|
||||
data=req)
|
||||
|
||||
@BasicInformationAPI.get('/class_avg_time_size', summary='统计每个班级的平均就业时长(只统计进入就业阶段的学生)')
|
||||
def get_class_avg_time_size_api(
|
||||
n:int=Query(..., description='页码,从1开始', ge=1),
|
||||
m:int=Query(..., description='每页条数', ge=1),
|
||||
db = Depends(get_db)):
|
||||
req,total = StatisticsDao.get_class_avg_time_size_dao(n,m,db)
|
||||
return ResponseModel6(code=200,
|
||||
message='查询成功',
|
||||
total=total,
|
||||
total_pages=ceil(total/m) if total > 0 else 0,
|
||||
data=req)
|
||||
@@ -0,0 +1,213 @@
|
||||
from model.statistics_model import StudentInfo, StudentScore, ClassInfo, EmploymentInfo
|
||||
from datetime import date
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from sqlalchemy import func # SQL 内置函数生成器(MIN/MAX/COUNT 等)
|
||||
|
||||
class StatisticsDao:
|
||||
@staticmethod
|
||||
def get_students_by_age_range_dao(n:int,m:int,min_age: int, max_age: int,db):
|
||||
# 年龄 → 出生日期区间(数据库存 birthday,没有 age 字段)
|
||||
today = date.today()
|
||||
earliest_birthday = today - relativedelta(years=max_age) # 最大年龄 → 最早出生
|
||||
latest_birthday = today - relativedelta(years=min_age) # 最小年龄 → 最晚出生
|
||||
try:
|
||||
q = db.query(StudentInfo).\
|
||||
filter(StudentInfo.is_deleted == '0').\
|
||||
filter(StudentInfo.birthday >= earliest_birthday).\
|
||||
filter(StudentInfo.birthday <= latest_birthday)
|
||||
total = q.count() # 总条数
|
||||
req = q.offset((n - 1) * m).limit(m).all() # 分页
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise e
|
||||
else:
|
||||
return req,total
|
||||
|
||||
@staticmethod
|
||||
def get_students_by_class_id_dao(class_id:str,db):
|
||||
try:
|
||||
q = db.query(StudentInfo).filter(StudentInfo.class_id == class_id).\
|
||||
filter(StudentInfo.is_deleted == '0')
|
||||
total_count = q.count() # 总人数
|
||||
male_count = q.filter(StudentInfo.gender == '男').count()
|
||||
female_count = q.filter(StudentInfo.gender == '女').count()
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise e
|
||||
else:
|
||||
return total_count, male_count , female_count
|
||||
|
||||
@staticmethod
|
||||
def get_students_by_score_dao(n:int,m:int,score:float,db):
|
||||
# GROUP BY 每个学生一行,HAVING MIN(score) > 阈值 → 所有成绩都过线
|
||||
# SELECT MIN(score) AS score → 返回该学生最低分(schema StudentInfo2 对应字段)
|
||||
try:
|
||||
q = (db.query(
|
||||
StudentInfo.student_name,
|
||||
StudentInfo.student_id,
|
||||
func.min(StudentScore.score).label('score') # 起别名,后面 i.score 访问
|
||||
).join(StudentScore, StudentInfo.student_id == StudentScore.student_id)
|
||||
.filter(StudentInfo.is_deleted == '0') # 逻辑删除:学生表
|
||||
.filter(StudentScore.is_deleted == '0') # 逻辑删除:成绩表
|
||||
.group_by(StudentInfo.student_id, StudentInfo.student_name) # only_full_group_by 要求
|
||||
.having(func.min(StudentScore.score) > score)) # 组最低分 > 阈值 = 全过线
|
||||
total = q.count()
|
||||
req = q.offset((n - 1) * m).limit(m).all()
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise e
|
||||
else:
|
||||
# Row 对象手动转字典,字段对齐 schema StudentInfo2
|
||||
return [{"student_name":i.student_name,
|
||||
'student_id':i.student_id,
|
||||
"score":i.score} for i in req],total
|
||||
|
||||
@staticmethod
|
||||
def get_student_by_no_pass_dao(n:int,m:int,fail_count:int,db):
|
||||
try:
|
||||
q = (db.query(
|
||||
StudentInfo.student_id,
|
||||
StudentInfo.student_name,
|
||||
func.count(StudentScore.score_id).label('fail_count')
|
||||
).join(StudentScore, StudentInfo.student_id == StudentScore.student_id)
|
||||
.filter(StudentInfo.is_deleted == '0')
|
||||
.filter(StudentScore.is_deleted == '0')
|
||||
.filter(StudentScore.is_pass == '0')
|
||||
.group_by(StudentInfo.student_id, StudentInfo.student_name)
|
||||
.having(func.count(StudentScore.score_id) >= fail_count))
|
||||
total = q.count()
|
||||
req = q.offset((n - 1) * m).limit(m).all()
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise e
|
||||
else:
|
||||
return [{"student_id": i.student_id,
|
||||
"student_name": i.student_name,
|
||||
"fail_count": i.fail_count} for i in req], total
|
||||
|
||||
@staticmethod
|
||||
def get_class_exam_avg_score_dao(n:int,m:int,db):
|
||||
# 统计每次考试每个班级的平均分,按平均分从高到低排序
|
||||
# 三表联查:student_score →(student_id)→ student_info →(class_id)→ class_info
|
||||
# 分组维度:exam_type + exam_date + course_id + class_id(一场考试×一个班级)
|
||||
# 聚合:AVG(score) 排序:DESC
|
||||
try:
|
||||
q = (db.query(
|
||||
StudentScore.course_id, # 课程编号(不同科目)
|
||||
StudentInfo.class_id, # 班级编号,来自学生表
|
||||
ClassInfo.class_name, # 班级名称,来自班级表
|
||||
func.avg(func.ifnull(StudentScore.score, 0)).label('avg_score') # 每个学生缺考分按0算,再求班级平均分
|
||||
).join(StudentInfo, StudentScore.student_id == StudentInfo.student_id) # 成绩→学生:取 class_id
|
||||
.join(ClassInfo, StudentInfo.class_id == ClassInfo.class_id) # 学生→班级:取 class_name
|
||||
.filter(StudentInfo.is_deleted == '0') # 过滤已删除学生
|
||||
.filter(StudentScore.is_deleted == '0') # 过滤已删除成绩
|
||||
.filter(ClassInfo.is_deleted == '0') # 过滤已停用班级
|
||||
.group_by(StudentScore.course_id,
|
||||
StudentInfo.class_id, # 每个班级单独成组
|
||||
ClassInfo.class_name) # MySQL only_full_group_by 要求
|
||||
.order_by(func.avg(func.ifnull(StudentScore.score,0)).desc())) # 平均分降序(从高到低)
|
||||
total = q.count() # 分组后的总组合数
|
||||
req = q.offset((n - 1) * m).limit(m).all() # 分页:从 (n-1)*m 开始取 m 条
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise e
|
||||
else:
|
||||
# Row 对象转 dict,avg_score 保留两位小数对齐前端展示
|
||||
return [{"course_id": i.course_id,
|
||||
"class_id": i.class_id,
|
||||
"class_name": i.class_name,
|
||||
"avg_score": round(float(i.avg_score), 2)} for i in req], total
|
||||
|
||||
@staticmethod
|
||||
def get_salary_top_dao(m:int,db): # 统计就业薪资最高的前五名学生的姓名,班级和就业时间,就业公司
|
||||
try:
|
||||
# 子查询:获取每个学生的最高薪资
|
||||
sq = (db.query(
|
||||
EmploymentInfo.student_id,
|
||||
func.max(EmploymentInfo.salary).label('max_salary')
|
||||
).filter(EmploymentInfo.is_deleted == '0')
|
||||
.group_by(EmploymentInfo.student_id)
|
||||
.order_by(func.max(EmploymentInfo.salary).desc())
|
||||
.limit(m)
|
||||
.subquery())
|
||||
|
||||
# 主查询:关联学生表、班级表和就业信息表
|
||||
q = (db.query(
|
||||
StudentInfo.student_name,
|
||||
ClassInfo.class_name,
|
||||
EmploymentInfo.part_time,
|
||||
EmploymentInfo.company_name,
|
||||
EmploymentInfo.salary
|
||||
).join(EmploymentInfo, StudentInfo.student_id == EmploymentInfo.student_id)
|
||||
.join(ClassInfo, StudentInfo.class_id == ClassInfo.class_id)
|
||||
.join(sq,
|
||||
(EmploymentInfo.student_id == sq.c.student_id) &
|
||||
(EmploymentInfo.salary == sq.c.max_salary))
|
||||
.filter(StudentInfo.is_deleted == '0')
|
||||
.filter(ClassInfo.is_deleted == '0')
|
||||
.filter(EmploymentInfo.is_deleted == '0'))
|
||||
|
||||
req = q.all()
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise e
|
||||
else:
|
||||
return [{"student_name": i.student_name,
|
||||
"class_name": i.class_name,
|
||||
"part_time": i.part_time,
|
||||
"company_name": i.company_name,
|
||||
"salary": i.salary} for i in req]
|
||||
|
||||
@staticmethod
|
||||
def get_time_size_dao(n:int,m:int,db): # 统计每个学生的就业时长(offer下发时间-就业开放时间)
|
||||
try:
|
||||
# 查询学生姓名、就业信息,并计算就业时长(天数)
|
||||
q = (db.query(
|
||||
StudentInfo.student_id,
|
||||
StudentInfo.student_name,
|
||||
EmploymentInfo.offer_date,
|
||||
EmploymentInfo.resume_open_date,
|
||||
func.datediff(EmploymentInfo.offer_date, EmploymentInfo.resume_open_date).label('time_size')
|
||||
).join(EmploymentInfo, StudentInfo.student_id == EmploymentInfo.student_id)
|
||||
.filter(StudentInfo.is_deleted == '0')
|
||||
.filter(EmploymentInfo.is_deleted == '0')
|
||||
.filter(EmploymentInfo.resume_open_date.isnot(None))) # 确保开放简历时间不为空
|
||||
|
||||
total = q.count()
|
||||
req = q.offset((n - 1) * m).limit(m).all()
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise e
|
||||
else:
|
||||
return [{"student_id": i.student_id,
|
||||
"student_name": i.student_name,
|
||||
"offer_date": i.offer_date,
|
||||
"resume_open_date": i.resume_open_date,
|
||||
"time_size": i.time_size} for i in req], total
|
||||
|
||||
@staticmethod
|
||||
def get_class_avg_time_size_dao(n:int,m:int,db):
|
||||
# 统计每个班级的平均就业时长(只统计进入就业阶段的学生)
|
||||
try:
|
||||
# 查询班级名称和平均就业时长
|
||||
q = (db.query(
|
||||
ClassInfo.class_id,
|
||||
ClassInfo.class_name,
|
||||
func.avg(func.datediff(EmploymentInfo.offer_date, EmploymentInfo.resume_open_date)).label('avg_time_size')
|
||||
).join(StudentInfo, ClassInfo.class_id == StudentInfo.class_id)
|
||||
.join(EmploymentInfo, StudentInfo.student_id == EmploymentInfo.student_id)
|
||||
.filter(ClassInfo.is_deleted == '0')
|
||||
.filter(StudentInfo.is_deleted == '0')
|
||||
.filter(EmploymentInfo.is_deleted == '0')
|
||||
.filter(EmploymentInfo.resume_open_date.isnot(None)) # 只统计有就业开放时间的学生
|
||||
.group_by(ClassInfo.class_id, ClassInfo.class_name))
|
||||
|
||||
total = q.count()
|
||||
req = q.offset((n - 1) * m).limit(m).all()
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise e
|
||||
else:
|
||||
return [{"class_id": i.class_id,
|
||||
"class_name": i.class_name,
|
||||
"avg_time_size": round(float(i.avg_time_size), 2)} for i in req], total
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import declarative_base,sessionmaker
|
||||
|
||||
db_url = "mysql+pymysql://root:123456@127.0.0.1:3306/student?charset=utf8mb4" #创建连接对象
|
||||
engine = create_engine(db_url) #与数据库进行连接
|
||||
|
||||
Base = declarative_base() # 执行函数,返回一个基类
|
||||
|
||||
|
||||
Session = sessionmaker(bind=engine
|
||||
,autoflush=False
|
||||
,autocommit = False
|
||||
)
|
||||
|
||||
def get_db():
|
||||
db = Session()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
@@ -0,0 +1,15 @@
|
||||
from fastapi import FastAPI
|
||||
from database import engine, Base
|
||||
from api.statistics_api import BasicInformationAPI
|
||||
from model import statistics_model #不可以删
|
||||
Base.metadata.create_all(engine) #创建所有的表
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.include_router(BasicInformationAPI)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run("main:app", host='0.0.0.0', port=12345)
|
||||
@@ -0,0 +1,118 @@
|
||||
from sqlalchemy import *
|
||||
from database import DATETIME,Base,Column,Integer,String
|
||||
|
||||
|
||||
class StudentInfo(Base):
|
||||
"""学生基本信息表"""
|
||||
__tablename__ = 'student_info'
|
||||
|
||||
student_id = Column(String(20), primary_key=True, comment='学生id')
|
||||
student_name = Column(String(50), nullable=False, comment='学生姓名')
|
||||
gender = Column(Enum('男', '女'), nullable=False, comment='性别')
|
||||
id_card = Column(String(18), nullable=False, comment='身份证号')
|
||||
birthday = Column(Date, nullable=False, comment='出生日期')
|
||||
ethnicity = Column(String(20), nullable=False, comment='民族')
|
||||
region_id = Column(String(20), ForeignKey('region.regio_id'),nullable=False, comment='籍贯编码,连接地区表')
|
||||
phone = Column(String(11), nullable=False, comment='手机号')
|
||||
major = Column(String(20), nullable=False, comment='就读专业')
|
||||
class_id = Column(String(20), nullable=False, comment='班级,连接班级表')
|
||||
enrollment_date = Column(Date, nullable=False, comment='入学日期')
|
||||
graduation_date = Column(Date, nullable=False, comment='毕业日期')
|
||||
student_status = Column(Enum('在读', '休学', '退学', '毕业'), nullable=False, comment='学籍状态')
|
||||
education_level = Column(Enum('大专', '本科', '硕士研究生', '博士研究生'), nullable=False, comment='学历')
|
||||
is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除,0=未删除, 1=已删除')
|
||||
create_time = Column(Date, nullable=False, comment='创建时间')
|
||||
update_time = Column(Date, nullable=False, comment='更新时间')
|
||||
|
||||
|
||||
class Region(Base):
|
||||
"""地区维度表"""
|
||||
__tablename__ = 'region'
|
||||
|
||||
regio_id = Column(String(50), primary_key=True, comment='地区编码')
|
||||
province = Column(String(50), nullable=False, comment='省/直辖市')
|
||||
city = Column(String(50), nullable=False, comment='市')
|
||||
district = Column(String(50), nullable=False, comment='区,县')
|
||||
is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除:0=未删除, 1=已删除')
|
||||
create_time = Column(DATETIME, nullable=False, comment='创建时间')
|
||||
update_time = Column(DATETIME, nullable=False, comment='更新时间')
|
||||
|
||||
class EmploymentInfo(Base):
|
||||
"""学生就业信息表"""
|
||||
__tablename__ = 'employment_info'
|
||||
|
||||
emp_id = Column(String(50), primary_key=True, comment='就业记录ID')
|
||||
student_id = Column(String(20), ForeignKey('student_info.student_id'), nullable=False,
|
||||
comment='学号,连接学生表')
|
||||
regio_id = Column(String(50), ForeignKey('region.regio_id'), nullable=False, comment='就业地区编码,连接地区表')
|
||||
job_name = Column(String(50), nullable=False, comment='就业岗位名称')
|
||||
company_name = Column(String(50), nullable=False, comment='就业公司名称')
|
||||
salary = Column(Float, nullable=False, comment='就业月薪(元)')
|
||||
offer_date = Column(Date, nullable=False, comment='offer下发/签约时间')
|
||||
part_time = Column(Date, nullable=False, comment='就业时间')
|
||||
resume_open_date = Column(Date, nullable=True, comment='开放简历时间')
|
||||
employment_status = Column(Enum('1', '2', '3', '4'), nullable=False,
|
||||
comment='就业状态:1=未就业, 2,=已就业,3=升学, 4=灵活就业')
|
||||
is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除:0=未删除, 1=已删除')
|
||||
create_time = Column(DATETIME, nullable=False, comment='创建时间')
|
||||
update_time = Column(DATETIME, nullable=False, comment='更新时间')
|
||||
|
||||
class StudentScore(Base):
|
||||
"""学生考核成绩表"""
|
||||
__tablename__ = 'student_score'
|
||||
|
||||
score_id = Column(Integer, primary_key=True, autoincrement=True, comment='成绩记录自增主键')
|
||||
student_id = Column(String(20), ForeignKey('student_info.student_id'),nullable=False,
|
||||
comment='学号,连接学生表')
|
||||
course_id = Column(String(20), ForeignKey('course_info.course_id'),nullable=False, comment='课程编号,连接课程表')
|
||||
exam_type = Column(Enum('期中', '期末', '月考', '周考', '模拟考'), nullable=False,
|
||||
comment='考试类型:1=期中, 2=期末, 3=月考, 4=周考,5=模拟考')
|
||||
score = Column(Float, nullable=True, comment='考试分数0~100,缺考为NULL')
|
||||
score_level = Column(String(10), nullable=False, comment='成绩等级')
|
||||
is_pass = Column(Enum('0', '1'), nullable=False, comment='是否及格:1=及格, 0=不及格(可由score>=60判断)')
|
||||
exam_date = Column(Date, nullable=False, comment='考试日期')
|
||||
exam_category = Column(Enum('首考', '重考', '补考'), nullable=False, comment='考试分类:1=首考, 2=补考, 3=重考')
|
||||
is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除:0=未删除, 1=已删除')
|
||||
create_time = Column(DATETIME, nullable=False, comment='创建时间')
|
||||
update_time = Column(DATETIME, nullable=False, comment='更新时间')
|
||||
|
||||
class ClassInfo(Base):
|
||||
"""班级信息表"""
|
||||
__tablename__ = 'class_info'
|
||||
|
||||
class_id = Column(String(50), primary_key=True, comment='班级业务编码')
|
||||
class_name = Column(String(50), nullable=False, comment='班级名称')
|
||||
grade_year = Column(Date, nullable=False, comment='入学年级')
|
||||
status = Column(Enum('1', '2', '3'), nullable=False, comment='班级状态:1=在读 2=已毕业 3=已停用')
|
||||
tags = Column(String(100), nullable=False, comment='存储多维标签,例如:["重点班", "科技特长", "2026届"]')
|
||||
is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除:0=未删除, 1=已删除')
|
||||
create_time = Column(DATETIME, nullable=False, comment='创建时间')
|
||||
update_time = Column(DATETIME, nullable=False, comment='更新时间')
|
||||
|
||||
class TeacherInfo(Base):
|
||||
"""教师基本信息表"""
|
||||
__tablename__ = 'teacher_info'
|
||||
|
||||
teacher_id = Column(String(20), primary_key=True, comment='教师编号')
|
||||
teacher_name = Column(String(20), nullable=False, comment='教师姓名')
|
||||
gender = Column(Enum('男', '女'), nullable=False, comment='性别')
|
||||
birthday = Column(Date, nullable=False, comment='出生日期')
|
||||
region_id = Column(String(50), ForeignKey('region.regio_id'), nullable=False, comment='籍贯地区编码')
|
||||
email = Column(String(50), nullable=False, comment='邮箱')
|
||||
phone = Column(String(11), nullable=False, comment='手机号码')
|
||||
graduation_school = Column(String(50), nullable=False, comment='毕业学校')
|
||||
teacher_rank = Column(Enum('1', '2', '3', '4'), nullable=False, comment='职称:1=初级, 2=中级, 3=高级, 4=特级')
|
||||
is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除:0=未删除, 1=已删除')
|
||||
create_time = Column(DATETIME, nullable=False, comment='创建时间')
|
||||
update_time = Column(DATETIME, nullable=False, comment='更新时间')
|
||||
|
||||
class CourseInfo(Base):
|
||||
"""课程基础信息"""
|
||||
__tablename__ = 'course_info'
|
||||
|
||||
course_id = Column(String(30), primary_key=True, comment='课程编号')
|
||||
course_name = Column(String(30), nullable=False, comment='课程名称')
|
||||
course_type = Column(Enum('1', '2', '3'), nullable=False, comment='课程类型:1 必修课 2 选修课 3 实训课')
|
||||
is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除:0=未删除, 1=已删除')
|
||||
create_time = Column(DATETIME, nullable=False, comment='创建时间')
|
||||
update_time = Column(DATETIME, nullable=False, comment='更新时间')
|
||||
@@ -0,0 +1,104 @@
|
||||
from pydantic import BaseModel,ConfigDict # FastAPI 推荐用 pydantic 做数据校验和序列化
|
||||
from datetime import date # 日期类型,用于 birthday、enrollment_date 等字段
|
||||
from typing import List # 类型提示,声明列表
|
||||
|
||||
class StudentInfo1(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
student_id: str # 学号
|
||||
student_name: str # 姓名
|
||||
gender: str # 性别(男/女)
|
||||
id_card: str # 身份证号
|
||||
birthday: date # 出生日期
|
||||
ethnicity: str # 民族
|
||||
region_id: str # 籍贯编码(关联 region表)
|
||||
phone: str # 手机号
|
||||
major: str # 专业
|
||||
class_id: str # 班级编号
|
||||
enrollment_date: date # 入学日期
|
||||
graduation_date: date # 毕业日期
|
||||
student_status: str # 学籍状态(在读/休学/退学/毕业)
|
||||
education_level: str # 学历(大专/本科/硕士/博士)
|
||||
|
||||
class ResponseModel(BaseModel):
|
||||
"""统一响应格式 — 所有接口都返回这个结构,方便前端统一处理"""
|
||||
code: int # 状态码,200=成功,400=参数错误
|
||||
message: str = 'ok' # 提示信息
|
||||
total:int #总条数
|
||||
total_pages:int #总页数
|
||||
data: List[StudentInfo1] # 真正的数据,这里是学员列表
|
||||
|
||||
class StudentInfo2(BaseModel):
|
||||
student_id: str
|
||||
student_name: str
|
||||
score:float
|
||||
|
||||
class ResponseModel1(BaseModel):
|
||||
"""统一响应格式 — 所有接口都返回这个结构,方便前端统一处理"""
|
||||
code: int # 状态码,200=成功,400=参数错误
|
||||
message: str = 'ok' # 提示信息
|
||||
total:int #总条数
|
||||
total_pages:int #总页数
|
||||
data: List[StudentInfo2] # 真正的数据,这里是学员列表
|
||||
|
||||
class StudentInfo3(BaseModel):
|
||||
student_id: str
|
||||
student_name: str
|
||||
fail_count: int
|
||||
|
||||
class ResponseModel2(BaseModel):
|
||||
code: int
|
||||
message: str = 'ok'
|
||||
total: int
|
||||
total_pages: int
|
||||
data: List[StudentInfo3]
|
||||
|
||||
class ClassExamAvg(BaseModel):
|
||||
course_id: str
|
||||
class_id: str
|
||||
class_name: str
|
||||
avg_score: float
|
||||
|
||||
class ResponseModel3(BaseModel):
|
||||
code: int
|
||||
message: str = 'ok'
|
||||
total: int
|
||||
total_pages: int
|
||||
data: List[ClassExamAvg]
|
||||
|
||||
class SalaryTop(BaseModel):
|
||||
student_name: str
|
||||
class_name: str
|
||||
company_name: str
|
||||
part_time: date
|
||||
salary: float
|
||||
|
||||
class ResponseModel4(BaseModel):
|
||||
code: int
|
||||
message: str = 'ok'
|
||||
data: List[SalaryTop]
|
||||
|
||||
class TimeSize(BaseModel):
|
||||
student_id: str
|
||||
student_name: str
|
||||
offer_date: date
|
||||
resume_open_date: date
|
||||
time_size: int # 就业时长(天数)
|
||||
|
||||
class ResponseModel5(BaseModel):
|
||||
code: int
|
||||
message: str = 'ok'
|
||||
total: int
|
||||
total_pages: int
|
||||
data: List[TimeSize]
|
||||
|
||||
class ClassAvgTimeSize(BaseModel):
|
||||
class_id: str
|
||||
class_name: str
|
||||
avg_time_size: float # 平均就业时长(天数)
|
||||
|
||||
class ResponseModel6(BaseModel):
|
||||
code: int
|
||||
message: str = 'ok'
|
||||
total: int
|
||||
total_pages: int
|
||||
data: List[ClassAvgTimeSize]
|
||||
Reference in New Issue
Block a user