Merge remote-tracking branch 'origin/lon_students_merge_code_test' into lon_students_merge_code_test
This commit is contained in:
@@ -0,0 +1,206 @@
|
|||||||
|
========================================
|
||||||
|
Class Management 班级管理模块
|
||||||
|
========================================
|
||||||
|
|
||||||
|
基于 FastAPI + SQLAlchemy + MySQL 的班级管理模块,
|
||||||
|
提供班级的增删改查、多字段分页查询、逻辑删除等能力。
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
一、目录结构
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
class_management/
|
||||||
|
api/
|
||||||
|
class_management_api.py # 路由层:定义 HTTP 接口
|
||||||
|
dao/
|
||||||
|
class_management_dao.py # 数据访问层:封装数据库操作
|
||||||
|
model/
|
||||||
|
class_management_model.py # ORM 模型:映射数据库表
|
||||||
|
schema/
|
||||||
|
class_management_request.py # Pydantic 模型:请求/响应体
|
||||||
|
database.py # 数据库连接、Session、Base
|
||||||
|
|
||||||
|
分层职责:
|
||||||
|
api 接收请求、参数校验、返回响应,不直接写 SQL
|
||||||
|
dao 封装 CRUD、事务控制,不处理 HTTP 状态码
|
||||||
|
model 定义表结构、字段约束,不写业务逻辑
|
||||||
|
schema 定义请求/响应格式,不碰数据库
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
二、数据表结构
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
表名:classes
|
||||||
|
|
||||||
|
字段 类型 约束 说明
|
||||||
|
------------------ ----------- ---------------------- ------------------
|
||||||
|
id Integer PK, Auto Increment 班级主键
|
||||||
|
num String(50) NOT NULL, UNIQUE 班级编号
|
||||||
|
name String(50) NOT NULL 班级名称
|
||||||
|
head_teacher_id Integer FK -> teachers.id 班主任编号
|
||||||
|
coach_teacher_id Integer FK -> teachers.id 授课老师编号
|
||||||
|
tutor_teacher_id Integer FK -> teachers.id 助教老师编号
|
||||||
|
class_start_time Date 可空 开班日期
|
||||||
|
class_end_time Date 可空 结课日期
|
||||||
|
is_delete Integer 默认 0 逻辑删除 0未删 1已删
|
||||||
|
create_time DATETIME 默认 now 创建时间
|
||||||
|
update_time DATETIME 默认 now, onupdate now 更新时间
|
||||||
|
|
||||||
|
注意:head_teacher_id / coach_teacher_id / tutor_teacher_id 是外键,
|
||||||
|
插入前需保证 teachers 表中存在对应 id。
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
三、API 接口
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
基础前缀:/classes
|
||||||
|
|
||||||
|
[1] 新增班级
|
||||||
|
POST /classes
|
||||||
|
|
||||||
|
请求体:
|
||||||
|
{
|
||||||
|
"num": "C001",
|
||||||
|
"name": "Python01班",
|
||||||
|
"head_teacher_id": 1,
|
||||||
|
"coach_teacher_id": 2,
|
||||||
|
"tutor_teacher_id": 3,
|
||||||
|
"class_start_time": "2025-01-01",
|
||||||
|
"class_end_time": "2025-06-30"
|
||||||
|
}
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
num string 班级编号,唯一
|
||||||
|
name string 班级名称
|
||||||
|
head_teacher_id int 班主任 ID
|
||||||
|
coach_teacher_id int 授课老师 ID
|
||||||
|
tutor_teacher_id int 助教老师 ID
|
||||||
|
class_start_time date 格式 YYYY-MM-DD
|
||||||
|
class_end_time date 格式 YYYY-MM-DD
|
||||||
|
|
||||||
|
响应:
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": "新增成功",
|
||||||
|
"totals": 1,
|
||||||
|
"data": { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
错误:
|
||||||
|
400 班级编号已存在
|
||||||
|
|
||||||
|
|
||||||
|
[2] 根据 ID 查询班级
|
||||||
|
GET /classes/{class_id}
|
||||||
|
|
||||||
|
响应:
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": "OK",
|
||||||
|
"totals": 1,
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"num": "C001",
|
||||||
|
"name": "Python01班",
|
||||||
|
"head_teacher_id": 1,
|
||||||
|
"coach_teacher_id": 2,
|
||||||
|
"tutor_teacher_id": 3,
|
||||||
|
"class_start_time": "2025-01-01",
|
||||||
|
"class_end_time": "2025-06-30"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
错误:
|
||||||
|
404 该班级不存在或已删除
|
||||||
|
|
||||||
|
[3] 多字段分页查询
|
||||||
|
GET /classes
|
||||||
|
|
||||||
|
Query 参数:
|
||||||
|
num string 班级编号(精确匹配)
|
||||||
|
name string 班级名称(精确匹配)
|
||||||
|
head_teacher_id int 班主任 ID
|
||||||
|
coach_teacher_id int 授课老师 ID
|
||||||
|
tutor_teacher_id int 助教老师 ID
|
||||||
|
class_start_time date 开班日期
|
||||||
|
class_end_time date 结课日期
|
||||||
|
page int 页码,默认 1,>=1
|
||||||
|
page_size int 每页条数,默认 5,1~20
|
||||||
|
|
||||||
|
请求示例:
|
||||||
|
GET /classes?name=Python01班&page=1&page_size=5
|
||||||
|
|
||||||
|
响应:
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": "OK",
|
||||||
|
"totals": 1,
|
||||||
|
"data": [
|
||||||
|
{ "id": 1, "num": "C001", "name": "Python01班", ... }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
注意:当前为内存分页,先查出所有匹配记录再切片。
|
||||||
|
数据量大时建议改成 DB 层 offset/limit。
|
||||||
|
|
||||||
|
[4] 修改班级信息
|
||||||
|
PUT /classes/{class_id}
|
||||||
|
|
||||||
|
请求体(只传要改的字段):
|
||||||
|
{
|
||||||
|
"head_teacher_id": 5,
|
||||||
|
"class_end_time": "2025-12-31"
|
||||||
|
}
|
||||||
|
|
||||||
|
响应:
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": "修改成功",
|
||||||
|
"data": { "id": 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
错误:
|
||||||
|
400 没有传入要修改的字段
|
||||||
|
404 班级不存在,修改失败
|
||||||
|
|
||||||
|
[5] 逻辑删除班级
|
||||||
|
DELETE /classes/{class_id}
|
||||||
|
|
||||||
|
响应:
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": "删除成功",
|
||||||
|
"data": { "id": 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
错误:
|
||||||
|
404 班级不存在,删除失败
|
||||||
|
|
||||||
|
说明:逻辑删除,只把 is_delete 置为 1,不物理删除记录。
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
五、约定与规范
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
返回值约定:
|
||||||
|
|
||||||
|
DAO 函数 成功返回 失败返回 API 判断
|
||||||
|
------------------ ---------- ------------ ------------------
|
||||||
|
add_class_dao None 错误字符串 if res:
|
||||||
|
update_class_dao int >= 1 0 if not row_count:
|
||||||
|
delete_class_dao int >= 1 0 if not del_rows:
|
||||||
|
get_class_dao list [] if not res:
|
||||||
|
|
||||||
|
逻辑删除:
|
||||||
|
所有查询默认过滤 is_delete = 0,
|
||||||
|
删除操作用 UPDATE is_delete = 1,不做物理删除。
|
||||||
|
|
||||||
|
分页:
|
||||||
|
page 从 1 开始
|
||||||
|
page_size 范围 1~20
|
||||||
|
返回 totals 为总条数,data 为当前页数据
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
模块负责人:cyf
|
||||||
|
最后更新:2026-09-22
|
||||||
@@ -50,11 +50,9 @@ def get_failing_students_dao(db):
|
|||||||
.order_by(ClassInfo.id, Score.num)
|
.order_by(ClassInfo.id, Score.num)
|
||||||
.all())
|
.all())
|
||||||
|
|
||||||
return [{"sid": r.sid,
|
return [{
|
||||||
"name": r.name,
|
"name": r.name,
|
||||||
"class_name": r.class_name,
|
"class_name": r.class_name,
|
||||||
"num": r.num,
|
|
||||||
"t_subject": r.tsub,
|
|
||||||
"score": r.score,}
|
"score": r.score,}
|
||||||
for r in results]
|
for r in results]
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ def get_students( stu:StudentsQuery = Depends()
|
|||||||
f'时间格式:YYYY-MM-DD\n'
|
f'时间格式:YYYY-MM-DD\n'
|
||||||
f'班级ID和顾问ID必须已经存在才可以绑定'
|
f'班级ID和顾问ID必须已经存在才可以绑定'
|
||||||
)
|
)
|
||||||
def add_student( stu:StudentsResponse = Body(...)
|
def add_student( stu:StudentsResponse =Depends()
|
||||||
, db=Depends(get_db)
|
, db=Depends(get_db)
|
||||||
):
|
):
|
||||||
s_dict = stu.model_dump(exclude_unset=True)
|
s_dict = stu.model_dump(exclude_unset=True)
|
||||||
@@ -43,7 +43,7 @@ def add_student( stu:StudentsResponse = Body(...)
|
|||||||
,description=f'请输入需要更新信息的学生ID,学生手机号不可重复,填写即更新,为空即不更新。\n '
|
,description=f'请输入需要更新信息的学生ID,学生手机号不可重复,填写即更新,为空即不更新。\n '
|
||||||
f'时间格式:YYYY-MM-DD'
|
f'时间格式:YYYY-MM-DD'
|
||||||
,response_model=StudentsUpdate )
|
,response_model=StudentsUpdate )
|
||||||
def get_students( stu:StudentsUpdate = Body(...)
|
def update_students( stu:StudentsUpdate =Depends()
|
||||||
, db=Depends(get_db)
|
, db=Depends(get_db)
|
||||||
):
|
):
|
||||||
s_dict = stu.model_dump(exclude_unset=True)
|
s_dict = stu.model_dump(exclude_unset=True)
|
||||||
@@ -54,10 +54,10 @@ def get_students( stu:StudentsUpdate = Body(...)
|
|||||||
|
|
||||||
@s_api.delete("/students"
|
@s_api.delete("/students"
|
||||||
,summary='删除学生资料'
|
,summary='删除学生资料'
|
||||||
,description=f'输入需要删除的学生信息,删除条件任选,但不可全为空,工资筛选能删除大于等于输入工资的所有人。\n '
|
,description=f'输入需要删除的学生信息,删除条件任选,但不可全为空。\n '
|
||||||
f'时间格式:YYYY-MM-DD'
|
f'时间格式:YYYY-MM-DD'
|
||||||
,response_model=list[StudentsDelete] ) # 逻辑删除/软删除
|
,response_model=list[StudentsDelete] ) # 逻辑删除/软删除
|
||||||
def delete_students( stu:StudentsDelete = Body(...)
|
def delete_students( stu:StudentsDelete =Depends()
|
||||||
, db=Depends(get_db)
|
, db=Depends(get_db)
|
||||||
):
|
):
|
||||||
s_dict = stu.model_dump(exclude_unset=True)
|
s_dict = stu.model_dump(exclude_unset=True)
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ class SexEnum(str , enum.Enum):
|
|||||||
m = '男'
|
m = '男'
|
||||||
f = '女'
|
f = '女'
|
||||||
|
|
||||||
|
class EducationEnum(str , enum.Enum):
|
||||||
|
z = '专科'
|
||||||
|
ben = '本科'
|
||||||
|
s = '硕士'
|
||||||
|
bo = '博士'
|
||||||
|
|
||||||
# 查询
|
# 查询
|
||||||
class StudentsQuery(BaseModel):
|
class StudentsQuery(BaseModel):
|
||||||
id:int | None = None
|
id:int | None = None
|
||||||
@@ -20,18 +26,14 @@ class StudentsQuery(BaseModel):
|
|||||||
specialty:str | None = None
|
specialty:str | None = None
|
||||||
enrollment_time:datetime | None = None
|
enrollment_time:datetime | None = None
|
||||||
graduate_time:datetime | None = None
|
graduate_time:datetime | None = None
|
||||||
education:str | None = None
|
education:EducationEnum | None = None
|
||||||
# 外键字段
|
# 外键字段
|
||||||
class_id:int | None = None
|
class_id:int | None = None
|
||||||
advisor_id:int | None = None
|
advisor_id:int | None = None
|
||||||
# 额外字段
|
# 额外字段
|
||||||
phone:str | None = None
|
phone:str | None = None
|
||||||
|
|
||||||
class EducationEnum(str , enum.Enum):
|
|
||||||
z = '专科'
|
|
||||||
ben = '本科'
|
|
||||||
s = '硕士'
|
|
||||||
bo = '博士'
|
|
||||||
|
|
||||||
# 新建
|
# 新建
|
||||||
class StudentsResponse(BaseModel):
|
class StudentsResponse(BaseModel):
|
||||||
@@ -47,7 +49,7 @@ class StudentsResponse(BaseModel):
|
|||||||
education:EducationEnum
|
education:EducationEnum
|
||||||
# 外键字段
|
# 外键字段
|
||||||
class_id:int = Field(..., gt=0, description='班级ID')
|
class_id:int = Field(..., gt=0, description='班级ID')
|
||||||
advisor_id:int | None = None
|
advisor_id:int = Field(..., gt=0, description='班级ID')
|
||||||
# 额外字段
|
# 额外字段
|
||||||
phone:str = Field(...,min_length=11,max_length=11,description='学生手机号')
|
phone:str = Field(...,min_length=11,max_length=11,description='学生手机号')
|
||||||
|
|
||||||
@@ -63,7 +65,7 @@ class StudentsUpdate(BaseModel):
|
|||||||
specialty:str | None = None
|
specialty:str | None = None
|
||||||
enrollment_time:datetime | None = None
|
enrollment_time:datetime | None = None
|
||||||
graduate_time:datetime | None = None
|
graduate_time:datetime | None = None
|
||||||
education:str | None = None
|
education:EducationEnum | None = None
|
||||||
# 外键字段
|
# 外键字段
|
||||||
class_id:int | None = None
|
class_id:int | None = None
|
||||||
advisor_id:int | None = None
|
advisor_id:int | None = None
|
||||||
@@ -82,7 +84,7 @@ class StudentsDelete(BaseModel):
|
|||||||
specialty:str | None = None
|
specialty:str | None = None
|
||||||
enrollment_time:datetime | None = None
|
enrollment_time:datetime | None = None
|
||||||
graduate_time:datetime | None = None
|
graduate_time:datetime | None = None
|
||||||
education:str | None = None
|
education:EducationEnum | None = None
|
||||||
# 外键字段
|
# 外键字段
|
||||||
class_id:int | None = None
|
class_id:int | None = None
|
||||||
advisor_id:int | None = None
|
advisor_id:int | None = None
|
||||||
|
|||||||
Reference in New Issue
Block a user