Compare commits
22
Commits
bca68a56f4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
236f2665a6 | ||
|
|
e80d463bbc | ||
|
|
a90c240b5a | ||
|
|
d70705e7fe | ||
|
|
ee468216cb | ||
|
|
56df0ef7b0 | ||
|
|
d90a89595f | ||
|
|
52452e378c | ||
|
|
4f90c6a5c3 | ||
|
|
1be706c267 | ||
|
|
6033504d76 | ||
|
|
9c24a229c7 | ||
|
|
c1deb9f545 | ||
|
|
fd3e52f4ff | ||
|
|
1933466e3d | ||
|
|
b14d316d0a | ||
|
|
9a0f2d533a | ||
|
|
2807c30683 | ||
|
|
6016a99bcf | ||
|
|
b3d557e4fc | ||
|
|
e65d7cd50b | ||
|
|
99eab62ab7 |
+1
-1
@@ -51,6 +51,6 @@ def get_classes( id:int|None = None # id参数接收前端输
|
||||
@classes_router.delete('/{id}',response_model=ClassResponse,summary='删除班级信息') # delete删除请求,指定返回数据格式为 ClassResponse
|
||||
def delete_classes(id:int,db=Depends(get_db)): # 定义删除接口函数,id参数接收前端输入的数据,参数db依赖于数据库会话的上下文管理器
|
||||
r = delete_classes_dao( id , db ) # 变量r用来接收dao层delete_classes_dao函数的返回值,执行函数时以以前端输入的id 数据、db依赖函数入参
|
||||
if r : # 判断 DAO 层的返回结果,如果是True
|
||||
if r!=0 : # 判断 DAO 层的返回结果,如果是True
|
||||
return ClassResponse( total=1,data='已成功删除班级信息' ) # 返回成功响应和提示给前端 # 如果是False
|
||||
raise HTTPException(status_code=500, detail='删除失败,该班级信息已不存在或服务器繁忙,请稍后更新!') # 如果为False,抛出 500 内部服务器错误 和提示
|
||||
+30
-16
@@ -6,7 +6,8 @@ from dao.employment import (create_employment,
|
||||
get_employment_by_student_id,
|
||||
get_employment_list,
|
||||
update_employment,
|
||||
delete_employment_logic)
|
||||
delete_employment_logic,
|
||||
recover_employment_delete)
|
||||
|
||||
from schemas.employment import (EmploymentCreate,
|
||||
EmploymentUpdate,
|
||||
@@ -28,17 +29,21 @@ def add_employment(body: EmploymentCreate, db: Session = Depends(get_db)):
|
||||
return res
|
||||
|
||||
|
||||
@employment_router.get("/students/{student_id}", response_model=EmploymentResponse, summary="查询指定学生的就业信息")
|
||||
def query_student_employment(student_id: int, db: Session = Depends(get_db)):
|
||||
"""根据学生id获取对应就业记录"""
|
||||
record = get_employment_by_student_id(db, student_id)
|
||||
if not record:
|
||||
raise HTTPException(status_code=404, detail="未找到该学生的就业信息")
|
||||
return record
|
||||
#
|
||||
|
||||
# @employment_router.get("/students/{student_id}", response_model=EmploymentResponse, summary="查询指定学生的就业信息")
|
||||
# def query_student_employment(student_id: int, db: Session = Depends(get_db)):
|
||||
# """根据学生id获取对应就业记录"""
|
||||
# record = get_employment_by_student_id(db, student_id)
|
||||
# if not record:
|
||||
# raise HTTPException(status_code=404, detail="未找到该学生的就业信息")
|
||||
# return record
|
||||
|
||||
|
||||
@employment_router.get("/", summary="就业信息列表,支持筛选")
|
||||
def query_employment_list(company_name: str | None = Query(None, description="公司名称模糊查询"),
|
||||
def query_employment_list(eid:int| None = Query(None, description="就业编号"),
|
||||
student_id:int| None = Query(None,description="学生id"),
|
||||
company_name: str | None = Query(None, description="公司名称模糊查询"),
|
||||
salary_min: float | None = Query(None, description="最低薪资"),
|
||||
salary_max: float | None = Query(None, description="最高薪资"),
|
||||
skip: int = Query(0, ge=0, description="偏移量"),
|
||||
@@ -49,13 +54,13 @@ def query_employment_list(company_name: str | None = Query(None, description="
|
||||
return {"total": total, "data": data_list}
|
||||
|
||||
|
||||
@employment_router.get("/{eid}", response_model=EmploymentResponse, summary="根据id查询就业详情")
|
||||
def query_employment_detail(eid: int, db: Session = Depends(get_db)):
|
||||
"""根据就业主键id查询单条就业记录"""
|
||||
record = get_employment_by_id(db, eid)
|
||||
if not record:
|
||||
raise HTTPException(status_code=404, detail="就业记录不存在或已被逻辑删除")
|
||||
return record
|
||||
# @employment_router.get("/{eid}", response_model=EmploymentResponse, summary="根据id查询就业详情")
|
||||
# def query_employment_detail(eid: int, db: Session = Depends(get_db)):
|
||||
# """根据就业主键id查询单条就业记录"""
|
||||
# record = get_employment_by_id(db, eid)
|
||||
# if not record:
|
||||
# raise HTTPException(status_code=404, detail="就业记录不存在或已被逻辑删除")
|
||||
# return record
|
||||
|
||||
|
||||
@employment_router.put("/{eid}", response_model=EmploymentResponse, summary="修改就业信息")
|
||||
@@ -75,3 +80,12 @@ def logic_delete_employment(eid: int, db: Session = Depends(get_db)):
|
||||
raise HTTPException(status_code=404, detail="删除失败,记录不存在")
|
||||
# 删除成功返回提示字典
|
||||
return {"code": 200, "msg": "逻辑删除成功"}
|
||||
|
||||
@employment_router.get("/{eid}", summary="回档逻辑删除就业记录")
|
||||
def recover_delete_employment(eid: int, db: Session = Depends(get_db)):
|
||||
"""执行逻辑删除,is_deleted置1,不会真正删除数据库行"""
|
||||
ok = recover_employment_delete(db, eid)
|
||||
if not ok:
|
||||
raise HTTPException(status_code=404, detail="回档失败,记录已经存在")
|
||||
# 删除成功返回提示字典
|
||||
return {"code": 200, "msg": "回档成功"}
|
||||
+1
-9
@@ -1,7 +1,7 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_db
|
||||
from dao.score import post_score_dao, update_score_dao, get_score_dao, get_all_scores_dao,delete_score_dao, exist_student_exam_dao
|
||||
from dao.score import post_score_dao, update_score_dao, get_score_dao,delete_score_dao, exist_student_exam_dao
|
||||
from schemas.score import ScoreCreate, ScoreUpdate, ScorePageResp
|
||||
|
||||
scores_router = APIRouter(prefix="/scores", tags=["成绩接口"])
|
||||
@@ -32,14 +32,6 @@ def list_score(
|
||||
|
||||
|
||||
|
||||
@scores_router.get("/all", response_model=ScorePageResp,
|
||||
summary="所有学生成绩接口",
|
||||
)
|
||||
def list_all_scores(page: int = Query(1, ge=1),
|
||||
page_size: int = Query(10, ge=1, le=100),
|
||||
db: Session = Depends(get_db)):
|
||||
total, data_list = get_all_scores_dao(page, page_size, db)
|
||||
return {"total": total, "data": data_list}
|
||||
|
||||
@scores_router.put("{score_id}", summary="更新成绩接口")
|
||||
def modify_score(score_id: int, req: ScoreUpdate, db: Session = Depends(get_db)):
|
||||
|
||||
+15
-14
@@ -3,6 +3,7 @@ from core.database import get_db
|
||||
from models.employment import Employment
|
||||
from models.student import Student
|
||||
from models.score import Score
|
||||
from schemas.statistics import *
|
||||
from dao.statistics import *
|
||||
from schemas.common import SuccessResponse
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -11,34 +12,34 @@ from sqlalchemy.orm import Session
|
||||
statistics_router=APIRouter(tags=['统计接口'])
|
||||
|
||||
|
||||
@statistics_router.get("/students/age-over-30",response_model=SuccessResponse)
|
||||
def api_age_over_thirty(db:Session=Depends(get_db)):
|
||||
@statistics_router.post("/students/age-over-30",response_model=SuccessResponse)
|
||||
def api_age_over_thirty(input_age:GenderCount,db:Session=Depends(get_db)):
|
||||
try:
|
||||
age_over_thirty_list=age_over_thirty(Student,db)
|
||||
age_over_thirty_list=age_over_thirty(Student,input_age.input_class,db)
|
||||
return SuccessResponse(status_code=200,msg=age_over_thirty_list)
|
||||
except NoExist as e:
|
||||
raise HTTPException(status_code=404,detail=str(e))
|
||||
# 这边功能函数返回的是一个字典
|
||||
@statistics_router.get("/classes/gender-count",response_model=SuccessResponse)
|
||||
def api_gender_count(db:Session=Depends(get_db)):
|
||||
def api_gender_count(input_class:int|None= None,db:Session=Depends(get_db)):
|
||||
try:
|
||||
result = gender_count(Student,db)
|
||||
result = gender_count(Student,db,input_class)
|
||||
return SuccessResponse(status_code=200,msg=result)
|
||||
except NoExist as e:
|
||||
raise HTTPException(status_code=404,detail=str(e))
|
||||
|
||||
@statistics_router.get("/scores/all-above-80",response_model=SuccessResponse)
|
||||
def all_above_eighty(db:Session=Depends(get_db)):
|
||||
@statistics_router.post("/scores/all-above-80",response_model=SuccessResponse)
|
||||
def all_above_eighty(page:int,score:StudentScore,db:Session=Depends(get_db)):
|
||||
try:
|
||||
result = score_over_eighty(Score,Student,db)
|
||||
result = score_over_eighty(score.score,page,Score,Student,db)
|
||||
return SuccessResponse(status_code=200,msg=result)
|
||||
except NoExist as e:
|
||||
raise HTTPException(status_code=404,detail=str(e))
|
||||
|
||||
@statistics_router.get("/scores/fail-more-than-two",response_model=SuccessResponse)
|
||||
def fail_two(db:Session=Depends(get_db)):
|
||||
def fail_two(num:int,offset_set:int,db:Session=Depends(get_db)):
|
||||
try:
|
||||
result = twice_error(Score,Student,db)
|
||||
result = twice_error(num,offset_set,Score,Student,db)
|
||||
return SuccessResponse(status_code=200,msg=result)
|
||||
except NoExist as e:
|
||||
raise HTTPException(status_code=404,detail=str(e))
|
||||
@@ -53,17 +54,17 @@ def class_average(db:Session=Depends(get_db)):
|
||||
|
||||
|
||||
@statistics_router.get("/employments/top5-salary",response_model=SuccessResponse)
|
||||
def top_salary(db:Session=Depends(get_db)):
|
||||
def top_salary(limit_set:int,db:Session=Depends(get_db)):
|
||||
try:
|
||||
result=max_salary(Student,Employment,db)
|
||||
result=max_salary(limit_set,Student,Employment,db)
|
||||
return SuccessResponse(status_code=200,msg=result)
|
||||
except NoExist as e:
|
||||
raise HTTPException(status_code=404,detail=str(e))
|
||||
|
||||
@statistics_router.get("/employments/student-duration",response_model=SuccessResponse)
|
||||
def employ_time(db:Session=Depends(get_db)):
|
||||
def employ_time(limit_set,db:Session=Depends(get_db)):
|
||||
try:
|
||||
result = student_market(Employment,db)
|
||||
result = student_market(limit_set,Employment,db)
|
||||
return SuccessResponse(status_code=200,msg=result)
|
||||
except NoExist as e:
|
||||
raise HTTPException(status_code=404,detail=str(e))
|
||||
|
||||
+36
-34
@@ -3,11 +3,11 @@ from fastapi import APIRouter, Depends, HTTPException,Query,Path
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.database import get_db
|
||||
from schemas.student import StudentCreate, StudentUpdate
|
||||
from schemas.student import StudentCreate, StudentUpdate, Gender
|
||||
from dao import student as student_dao
|
||||
student_router = APIRouter()
|
||||
|
||||
@student_router.put("",summary='添加学生')
|
||||
@student_router.post("",summary='添加学生')
|
||||
def create_student(request:StudentCreate,db:Session = Depends(get_db)):
|
||||
d = request.model_dump()
|
||||
r = student_dao.create_students(d,db)
|
||||
@@ -15,7 +15,7 @@ def create_student(request:StudentCreate,db:Session = Depends(get_db)):
|
||||
raise HTTPException(status_code=500,detail='服务器繁忙,请稍后添加!')
|
||||
return {'code':200,'detail':"添加学生成功"}
|
||||
|
||||
@student_router.post("/{id}", summary="更新学生信息")
|
||||
@student_router.put("/{id}", summary="更新学生信息")
|
||||
def update_student(
|
||||
id: int,
|
||||
request: StudentUpdate,
|
||||
@@ -51,6 +51,7 @@ def update_student(
|
||||
@student_router.get("", summary="分页查询学生列表")
|
||||
def get_students(
|
||||
# 三个查询条件都可以不传
|
||||
gender:Gender = Query(default=None),
|
||||
student_no: str | None = Query(default=None,description="按学号精确查询"),
|
||||
student_name: str | None = Query(default=None,description="按名字模糊查询"),
|
||||
class_id: int | None = Query(default=None,gt=0,description="按班级号精确查询"),
|
||||
@@ -67,6 +68,7 @@ def get_students(
|
||||
class_id=class_id,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
gender = gender
|
||||
)
|
||||
data = [
|
||||
{
|
||||
@@ -95,37 +97,37 @@ def get_students(
|
||||
"data": data,
|
||||
}
|
||||
|
||||
@student_router.get("/{id}", summary="查询学生详情")
|
||||
def get_student(id: int = Path(gt=0),
|
||||
db: Session = Depends(get_db)):
|
||||
# 根据路径中的 ID 查询学生
|
||||
student = student_dao.get_student_by_id(id, db)
|
||||
|
||||
if not student:
|
||||
# 404 表示请求的数据不存在
|
||||
raise HTTPException(status_code=404, detail="学生不存在")
|
||||
|
||||
result = {
|
||||
"id": student.id,
|
||||
"student_no": student.student_no,
|
||||
"student_name": student.student_name,
|
||||
"class_id": student.class_id,
|
||||
"consultant_id": student.consultant_id,
|
||||
"native_place": student.native_place,
|
||||
"graduation_school": student.graduation_school,
|
||||
"major": student.major,
|
||||
"enrollment_date": student.enrollment_date,
|
||||
"graduation_date": student.graduation_date,
|
||||
"education": student.education,
|
||||
"age": student.age,
|
||||
"gender": student.gender,
|
||||
}
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"detail": "查询成功",
|
||||
"data": result,
|
||||
}
|
||||
# @student_router.get("/{id}", summary="查询学生详情")
|
||||
# def get_student(id: int = Path(gt=0),
|
||||
# db: Session = Depends(get_db)):
|
||||
# # 根据路径中的 ID 查询学生
|
||||
# student = student_dao.get_student_by_id(id, db)
|
||||
#
|
||||
# if not student:
|
||||
# # 404 表示请求的数据不存在
|
||||
# raise HTTPException(status_code=404, detail="学生不存在")
|
||||
#
|
||||
# result = {
|
||||
# "id": student.id,
|
||||
# "student_no": student.student_no,
|
||||
# "student_name": student.student_name,
|
||||
# "class_id": student.class_id,
|
||||
# "consultant_id": student.consultant_id,
|
||||
# "native_place": student.native_place,
|
||||
# "graduation_school": student.graduation_school,
|
||||
# "major": student.major,
|
||||
# "enrollment_date": student.enrollment_date,
|
||||
# "graduation_date": student.graduation_date,
|
||||
# "education": student.education,
|
||||
# "age": student.age,
|
||||
# "gender": student.gender,
|
||||
# }
|
||||
#
|
||||
# return {
|
||||
# "code": 200,
|
||||
# "detail": "查询成功",
|
||||
# "data": result,
|
||||
# }
|
||||
|
||||
@student_router.delete("/{id}", summary="逻辑删除学生")
|
||||
def delete_student(id: int, db: Session = Depends(get_db)):
|
||||
|
||||
+4
-4
@@ -73,10 +73,10 @@ def get_classes_dao( class_id, class_no , class_name , page , page_size , db ):
|
||||
# ————定义班级表"删除"信息的函数————
|
||||
def delete_classes_dao( class_id , db ): # 定义函数,设置两个个形参class_id,db
|
||||
try : # 执行以下可能报错的代码
|
||||
db.query( Classes ).filter( Classes.id == class_id , Classes.is_deleted == 0 ).update( {"is_deleted": 1} ) # 将未删除且前端输入的id和数据库id可以匹配上的数据更新is_deleted字段为1,表示已逻辑删除
|
||||
r = db.query( Classes ).filter( Classes.id == class_id , Classes.is_deleted == 0 ).update( {"is_deleted": 1} ) # 将未删除且前端输入的id和数据库id可以匹配上的数据更新is_deleted字段为1,表示已逻辑删除
|
||||
if r !=0:
|
||||
db.commit()
|
||||
return r
|
||||
except : # 捕获异常:
|
||||
db.rollback() # 如果中间出错,回滚事务,防止产生脏数据
|
||||
return False # 返回 False,返回接口,表示操作失败
|
||||
else : # 执行成功:
|
||||
db.commit() # 提交事务:更新is_deleted字段为1,表示已逻辑删除,前端无法查询到已逻辑删除的数据
|
||||
return True # 返回 True,返回接口,表示操作成功
|
||||
|
||||
+41
-24
@@ -1,13 +1,12 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from models.employment import Employment
|
||||
from schemas.employment import EmploymentCreate, EmploymentUpdate, EmploymentResponse
|
||||
from schemas.employment import EmploymentResponse
|
||||
|
||||
|
||||
def create_employment(db, obj_in):
|
||||
def create_employment(db, e):
|
||||
# obj_in是Pydantic对象(EmploymentCreate)
|
||||
"""新增就业信息:一个学生只能存在一条就业记录"""
|
||||
try:
|
||||
db_obj = Employment(**obj_in.model_dump())
|
||||
db_obj = Employment(**e.model_dump())
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
@@ -75,10 +74,14 @@ def get_employment_by_student_id(db, student_id):
|
||||
return None
|
||||
|
||||
|
||||
def get_employment_list(db, company_name=None, salary_min=None, salary_max=None, skip=0, limit=20):
|
||||
def get_employment_list(db, id=None,student_id=None,company_name=None, salary_min=None, salary_max=None, skip=0, limit=20):
|
||||
"""就业列表查询;支持公司名模糊、薪资范围筛选;只查询is_deleted=0"""
|
||||
try:
|
||||
query = db.query(Employment).filter(Employment.is_deleted == 0)
|
||||
if id is not None:
|
||||
query = query.filter(Employment.id >= id)
|
||||
if student_id is not None:
|
||||
query = query.filter(Employment.student_id >= student_id)
|
||||
if company_name:
|
||||
query = query.filter(Employment.company_name.like(f"%{company_name}%"))
|
||||
if salary_min is not None:
|
||||
@@ -108,48 +111,62 @@ def get_employment_list(db, company_name=None, salary_min=None, salary_max=None,
|
||||
return [], 0
|
||||
|
||||
|
||||
def update_employment(db, eid, obj_in):
|
||||
def update_employment(db, eid, e):
|
||||
"""修改就业信息,只更新传入的字段"""
|
||||
try:
|
||||
db_obj_raw = db.query(Employment).filter(Employment.id == eid
|
||||
x = db.query(Employment).filter(Employment.id == eid
|
||||
,Employment.is_deleted == 0).first()
|
||||
if db_obj_raw is None:
|
||||
if x is None:
|
||||
return None
|
||||
|
||||
update_dict = obj_in.model_dump(exclude_unset=True)
|
||||
update_dict = e.model_dump(exclude_unset=True)
|
||||
db.query(Employment).filter(Employment.id == eid
|
||||
,Employment.is_deleted == 0).update(update_dict)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_obj_raw)
|
||||
db.refresh(x)
|
||||
|
||||
resp = EmploymentResponse(id=db_obj_raw.id
|
||||
,student_id=db_obj_raw.student_id
|
||||
,employment_status=db_obj_raw.employment_status
|
||||
,employment_open_date=db_obj_raw.employment_open_date
|
||||
,offer_date=db_obj_raw.offer_date
|
||||
,company_name=db_obj_raw.company_name
|
||||
,salary=db_obj_raw.salary
|
||||
,remark=db_obj_raw.remark
|
||||
,created_at=db_obj_raw.created_at
|
||||
,updated_at=db_obj_raw.updated_at)
|
||||
resp = EmploymentResponse(id=x.id
|
||||
,student_id=x.student_id
|
||||
,employment_status=x.employment_status
|
||||
,employment_open_date=x.employment_open_date
|
||||
,offer_date=x.offer_date
|
||||
,company_name=x.company_name
|
||||
,salary=x.salary
|
||||
,remark=x.remark
|
||||
,created_at=x.created_at
|
||||
,updated_at=x.updated_at)
|
||||
return resp.model_dump()
|
||||
|
||||
except Exception:
|
||||
db.rollback()
|
||||
return None
|
||||
return
|
||||
|
||||
|
||||
|
||||
def delete_employment_logic(db, eid):
|
||||
"""逻辑删除,只修改is_deleted=1,不做物理删除"""
|
||||
try:
|
||||
db_obj_raw = db.query(Employment).filter(Employment.id == eid
|
||||
y = db.query(Employment).filter(Employment.id == eid
|
||||
,Employment.is_deleted == 0).first()
|
||||
if db_obj_raw is None:
|
||||
if y is None:
|
||||
return False
|
||||
db_obj_raw.is_deleted = 1
|
||||
y.is_deleted = 1
|
||||
db.commit()
|
||||
return True
|
||||
except Exception:
|
||||
db.rollback()
|
||||
return False
|
||||
|
||||
def recover_employment_delete(db, eid):
|
||||
"""逻辑删除,只修改is_deleted=1,不做物理删除"""
|
||||
try:
|
||||
y = db.query(Employment).filter(Employment.id == eid).first()
|
||||
if y is None:
|
||||
return False
|
||||
y.is_deleted = 0
|
||||
db.commit()
|
||||
return True
|
||||
except Exception:
|
||||
db.rollback()
|
||||
return False
|
||||
+32
-33
@@ -5,23 +5,27 @@ def post_score_dao(data_dict, db):
|
||||
try:
|
||||
obj = Score(**data_dict)
|
||||
db.add(obj)
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
print("新增成绩异常:", e)
|
||||
db.rollback()
|
||||
return False
|
||||
else:
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
def update_score_dao(score_id, db, updates):
|
||||
"""修改成绩,query.update方式"""
|
||||
try:
|
||||
db.query(Score).filter(
|
||||
cnt = db.query(Score).filter(
|
||||
Score.id == score_id,
|
||||
Score.is_deleted == 0
|
||||
).update(updates)
|
||||
except Exception:
|
||||
if cnt == 0:
|
||||
db.rollback()
|
||||
return False
|
||||
except Exception as e:
|
||||
print("修改成绩异常:", e)
|
||||
db.rollback()
|
||||
return False
|
||||
else:
|
||||
@@ -30,14 +34,25 @@ def update_score_dao(score_id, db, updates):
|
||||
|
||||
|
||||
def get_score_dao(score_id, student_id, exam_seq, page, page_size, db):
|
||||
"""分页查询成绩,多条件过滤,只查未删除"""
|
||||
"""分页查询成绩,多条件过滤,只查未删除
|
||||
规则:
|
||||
- score_id == 0 → 查所有学生成绩(仅分页,不加 id 过滤)
|
||||
- 三个筛选条件全为空(None) → 直接返回空,不查库
|
||||
- 其他情况 → 按传入条件过滤
|
||||
"""
|
||||
q = db.query(Score).filter(Score.is_deleted == 0)
|
||||
if score_id:
|
||||
q = q.filter(Score.id == score_id)
|
||||
if student_id:
|
||||
q = q.filter(Score.student_id == student_id)
|
||||
if exam_seq:
|
||||
q = q.filter(Score.exam_seq == exam_seq)
|
||||
|
||||
if score_id == 0:
|
||||
pass # score_id=0 → 查全部(分页)
|
||||
elif score_id is None and not student_id and not exam_seq:
|
||||
return 0, [] # 三个参数都没传 → 返回空
|
||||
else:
|
||||
if score_id:
|
||||
q = q.filter(Score.id == score_id)
|
||||
if student_id:
|
||||
q = q.filter(Score.student_id == student_id)
|
||||
if exam_seq:
|
||||
q = q.filter(Score.exam_seq == exam_seq)
|
||||
|
||||
total = q.count()
|
||||
rows = q.offset((page - 1) * page_size).limit(page_size).all()
|
||||
@@ -58,36 +73,20 @@ def get_score_dao(score_id, student_id, exam_seq, page, page_size, db):
|
||||
]
|
||||
return total, res_list
|
||||
|
||||
def get_all_scores_dao(page, page_size, db):
|
||||
r = db.query(Score).filter(Score.is_deleted == 0)\
|
||||
.order_by(Score.student_id, Score.exam_seq)
|
||||
total = r.count() # 总条数
|
||||
rows = r.offset((page-1)*page_size).limit(page_size).all()# 分页
|
||||
res_list_all = [
|
||||
{
|
||||
"id": i.id,
|
||||
"student_id": i.student_id,
|
||||
"score": i.score,
|
||||
"exam_seq": i.exam_seq,
|
||||
"exam_date": i.exam_date,
|
||||
"remark": i.remark,
|
||||
"created_at": i.created_at,
|
||||
"updated_at": i.updated_at,
|
||||
"is_deleted": i.is_deleted
|
||||
}
|
||||
for i in rows
|
||||
]
|
||||
return total,res_list_all
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def delete_score_dao(score_id, db):
|
||||
"""逻辑删除,更新is_deleted=1"""
|
||||
try:
|
||||
db.query(Score).filter(
|
||||
cnt = db.query(Score).filter(
|
||||
Score.id == score_id,
|
||||
Score.is_deleted == 0
|
||||
).update({"is_deleted": 1})
|
||||
).update({"is_deleted": 1}) # ← 接住返回值
|
||||
if cnt == 0: # ← 关键:一行都没匹配到
|
||||
db.rollback()
|
||||
return False
|
||||
except Exception as err:
|
||||
print("删除成绩异常:", err)
|
||||
db.rollback()
|
||||
|
||||
+22
-16
@@ -3,9 +3,8 @@ from sqlalchemy import func
|
||||
|
||||
#`GET /statistics/students/age-over-30`
|
||||
#查询所有超过 30 岁的学员信息
|
||||
|
||||
def age_over_thirty(Student,db):
|
||||
student_list=db.query(Student).filter(Student.age>30,Student.is_deleted==0).all()
|
||||
def age_over_thirty(Student,input_age,db):
|
||||
student_list=db.query(Student).filter(Student.age>input_age,Student.is_deleted==0).all()
|
||||
if not student_list :
|
||||
raise NoExist('无目标年龄大于30岁')
|
||||
# 把每个学生对象需要返回的字段,明确放进普通字典。
|
||||
@@ -30,9 +29,13 @@ def age_over_thirty(Student,db):
|
||||
"is_deleted": student.is_deleted,
|
||||
})
|
||||
return result
|
||||
|
||||
def gender_count(Student,db):
|
||||
result=db.query(Student.class_id,Student.gender,func.count(Student.id)).filter(Student.is_deleted==0).group_by(Student.class_id,Student.gender).all()
|
||||
#统计男、女、总人数
|
||||
def gender_count(Student,db,input_class=None):
|
||||
if input_class :
|
||||
result = db.query(Student.class_id, Student.gender, func.count(Student.id)).filter(
|
||||
Student.class_id == input_class, Student.is_deleted == 0).group_by(Student.class_id, Student.gender).all()
|
||||
else:
|
||||
result=db.query(Student.class_id,Student.gender,func.count(Student.id)).filter(Student.is_deleted==0).group_by(Student.class_id,Student.gender).all()
|
||||
if not result :
|
||||
raise NoExist('班里招点人吧,要倒闭了')
|
||||
dict1 = {}
|
||||
@@ -43,10 +46,11 @@ def gender_count(Student,db):
|
||||
dict1[i]['总人数']+=k
|
||||
return dict1
|
||||
#查询每次考试成绩都在 80 分以上的学生编号、姓名和成绩
|
||||
def score_over_eighty(Score,Student,db):
|
||||
#首先,拿到最低值>80的人的列表,row。然后,遍历元素拿到符合条件的id的属性。
|
||||
def score_over_eighty(score,offset_set,Score,Student,db):
|
||||
student_list=db.query(Student.id, Student.student_name)\
|
||||
.join(Score,Score.student_id==Student.id).filter(Student.is_deleted==0,Score.is_deleted==0).group_by(Student.id,Student.student_name)\
|
||||
.having(func.min(Score.score)>80).all()
|
||||
.having(func.min(Score.score)>score).all()
|
||||
if not student_list :
|
||||
raise NoExist('全是学渣')
|
||||
list1 = []
|
||||
@@ -59,10 +63,11 @@ def score_over_eighty(Score,Student,db):
|
||||
"student_name": student_name,
|
||||
"score": float(score) if score is not None else None,
|
||||
})
|
||||
return list1
|
||||
list2=list1[5*(offset_set-1):5*offset_set]
|
||||
return list2
|
||||
#查询有两次以上不及格的学生姓名、班级和不及格成绩
|
||||
def twice_error(Score,Student,db):
|
||||
student_list=db.query(Score.student_id).filter(Score.score<60,Score.is_deleted==0).group_by(Score.student_id).having(func.count(Score.id)>2).all()
|
||||
def twice_error(num,offset_set,Score,Student,db):
|
||||
student_list=db.query(Score.student_id).filter(Score.score<60,Score.is_deleted==0).group_by(Score.student_id).having(func.count(Score.id)>num).all()
|
||||
if not student_list :
|
||||
raise NoExist('全是学霸')
|
||||
list1=[]
|
||||
@@ -75,7 +80,8 @@ def twice_error(Score,Student,db):
|
||||
"class_id": class_id,
|
||||
"score": float(score) if score is not None else None,
|
||||
})
|
||||
return list1
|
||||
list2 = list1[5 * (offset_set - 1):5 * offset_set]
|
||||
return list2
|
||||
|
||||
#统计每次考试每个班级的平均分,并按照平均分从高到低排序
|
||||
def avg_class(Score,Student,db):
|
||||
@@ -92,8 +98,8 @@ def avg_class(Score,Student,db):
|
||||
return result
|
||||
|
||||
#统计就业薪资最高的前五名学生姓名、班级、就业时间、就业公司
|
||||
def max_salary(Student,Employment,db):
|
||||
student_list = db.query(Student.student_name,Student.class_id,Employment.offer_date,Employment.company_name).join(Employment, Student.id == Employment.student_id).filter(Student.is_deleted==0,Employment.is_deleted==0).order_by(Employment.salary.desc()).limit(5).all()
|
||||
def max_salary(limit_set,Student,Employment,db):
|
||||
student_list = db.query(Student.student_name,Student.class_id,Employment.offer_date,Employment.company_name).join(Employment, Student.id == Employment.student_id).filter(Student.is_deleted==0,Employment.is_deleted==0).order_by(Employment.salary.desc()).limit(limit_set).all()
|
||||
if not student_list :
|
||||
raise NoExist('没就业的啊兄弟?')
|
||||
result = []
|
||||
@@ -108,8 +114,8 @@ def max_salary(Student,Employment,db):
|
||||
|
||||
# - 统计每个学生的就业时长
|
||||
# - 就业时长 = `offer_date - employment_open_date`
|
||||
def student_market(Employment,db):
|
||||
student_list = db.query(Employment.student_id,func.datediff(Employment.offer_date,Employment.employment_open_date)).filter(Employment.is_deleted==0).all()
|
||||
def student_market(limit_set,Employment,db):
|
||||
student_list = db.query(Employment.student_id,func.datediff(Employment.offer_date,Employment.employment_open_date)).filter(Employment.is_deleted==0).limit(limit_set).all()
|
||||
result = []
|
||||
for student_id, duration_days in student_list:
|
||||
result.append({
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from models.student import Student
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from schemas.student import Gender
|
||||
|
||||
|
||||
#新建学生
|
||||
def create_students(d: dict, db: Session):
|
||||
stu = Student(**d)
|
||||
@@ -62,6 +65,7 @@ def get_students(
|
||||
class_id: int | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 10,
|
||||
gender: Gender = None,
|
||||
):
|
||||
# 第一步:只查询没有被删除的学生
|
||||
query = db.query(Student).filter(Student.is_deleted == 0)
|
||||
@@ -71,6 +75,10 @@ def get_students(
|
||||
# 学号使用精确查询
|
||||
query = query.filter(Student.student_no == student_no)
|
||||
|
||||
if gender:
|
||||
#查询枚举性别
|
||||
query = query.filter(Student.gender == gender)
|
||||
|
||||
if student_name:
|
||||
# 姓名使用模糊查询,例如“张”可以查到“张三”
|
||||
query = query.filter(Student.student_name.like(f"%{student_name}%"))
|
||||
|
||||
@@ -0,0 +1,472 @@
|
||||
# 学生模块答辩演示步骤
|
||||
|
||||
> 演示目标:通过 Swagger 依次展示学生新增、参数校验、分页查询、条件查询、详情查询、局部更新和逻辑删除。
|
||||
|
||||
## 一、答辩前检查
|
||||
|
||||
### 1. 确认数据库和基础数据
|
||||
|
||||
1. 确认 MySQL 已启动。
|
||||
2. 确认项目配置的数据库可以连接。
|
||||
3. 确认班级表中至少存在一条未删除的班级记录。
|
||||
4. 记住一个真实存在的班级 ID。下文示例使用 `class_id = 1`,如果数据库中不存在班级 1,请替换成实际班级 ID。
|
||||
|
||||
`consultant_id` 在演示数据中使用 `null`,避免因为顾问外键不存在导致新增失败。
|
||||
|
||||
### 2. 确认当前接口方法
|
||||
|
||||
当前代码中的学生接口如下:
|
||||
|
||||
| 功能 | 请求方法 | 地址 |
|
||||
| --- | --- | --- |
|
||||
| 新增学生 | `PUT` | `/students` |
|
||||
| 更新学生 | `POST` | `/students/{id}` |
|
||||
| 分页查询 | `GET` | `/students` |
|
||||
| 详情查询 | `GET` | `/students/{id}` |
|
||||
| 逻辑删除 | `DELETE` | `/students/{id}` |
|
||||
|
||||
注意:常见 REST 规范通常使用 `POST` 新增、`PUT` 更新,但当前项目代码正好相反。答辩前如果修改了请求方法,需要同步调整本文档中的操作。
|
||||
|
||||
### 3. 启动项目
|
||||
|
||||
在项目根目录执行:
|
||||
|
||||
```powershell
|
||||
python main.py
|
||||
```
|
||||
|
||||
看到服务正常启动后,在浏览器打开:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:23333/docs
|
||||
```
|
||||
|
||||
在 Swagger 页面找到“学生接口”。
|
||||
|
||||
## 二、正式演示流程
|
||||
|
||||
建议严格按照下面的顺序演示。每次操作时先展开接口,点击 **Try it out**,填写参数或请求体,再点击 **Execute**。
|
||||
|
||||
## 步骤 1:演示新增参数校验
|
||||
|
||||
展开:
|
||||
|
||||
```text
|
||||
PUT /students
|
||||
```
|
||||
|
||||
先输入一条性别不合法的数据:
|
||||
|
||||
```json
|
||||
{
|
||||
"student_name": "答辩演示学生",
|
||||
"class_id": 1,
|
||||
"consultant_id": null,
|
||||
"native_place": "河南",
|
||||
"graduation_school": "演示大学",
|
||||
"major": "软件工程",
|
||||
"enrollment_date": "2025-09-01",
|
||||
"graduation_date": "2027-06-30",
|
||||
"education": "本科",
|
||||
"age": 22,
|
||||
"gender": "未知"
|
||||
}
|
||||
```
|
||||
|
||||
预期结果:HTTP 状态码为 `422`,提示 `gender` 只能是“男”或“女”。
|
||||
|
||||
讲解词:
|
||||
|
||||
> 请求进入接口之前会先经过 Pydantic 请求模型校验。性别字段使用 Literal 约束,只允许男或女,非法数据不会进入 DAO 层,也不会写入数据库。
|
||||
|
||||
## 步骤 2:演示日期校验
|
||||
|
||||
仍然使用新增接口,将毕业日期设置为早于入学日期:
|
||||
|
||||
```json
|
||||
{
|
||||
"student_name": "答辩演示学生",
|
||||
"class_id": 1,
|
||||
"consultant_id": null,
|
||||
"native_place": "河南",
|
||||
"graduation_school": "演示大学",
|
||||
"major": "软件工程",
|
||||
"enrollment_date": "2025-09-01",
|
||||
"graduation_date": "2024-06-30",
|
||||
"education": "本科",
|
||||
"age": 22,
|
||||
"gender": "男"
|
||||
}
|
||||
```
|
||||
|
||||
预期结果:HTTP 状态码为 `422`,提示“毕业日期不能早于入学日期”。
|
||||
|
||||
讲解词:
|
||||
|
||||
> 除了单字段类型和范围校验,我还使用模型级校验器校验两个日期之间的业务关系。
|
||||
|
||||
## 步骤 3:新增一名合法学生
|
||||
|
||||
继续使用:
|
||||
|
||||
```text
|
||||
PUT /students
|
||||
```
|
||||
|
||||
输入以下合法数据:
|
||||
|
||||
```json
|
||||
{
|
||||
"student_name": "答辩演示学生",
|
||||
"class_id": 1,
|
||||
"consultant_id": null,
|
||||
"native_place": "河南",
|
||||
"graduation_school": "演示大学",
|
||||
"major": "软件工程",
|
||||
"enrollment_date": "2025-09-01",
|
||||
"graduation_date": "2027-06-30",
|
||||
"education": "本科",
|
||||
"age": 22,
|
||||
"gender": "男"
|
||||
}
|
||||
```
|
||||
|
||||
这里故意不传 `student_no`。DAO 在插入并取得自增主键后,会自动生成类似 `STU0001` 的学生编号。
|
||||
|
||||
预期响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"detail": "添加学生成功"
|
||||
}
|
||||
```
|
||||
|
||||
讲解词:
|
||||
|
||||
> 新增请求先由 StudentCreate 完成字段校验,再通过 model_dump 转成字典。DAO 创建 ORM 实例并执行 flush 获取自增 ID,如果没有手动传入学号,就根据 ID 自动生成学号,最后提交事务。
|
||||
|
||||
## 步骤 4:分页查询并记录演示学生 ID
|
||||
|
||||
展开:
|
||||
|
||||
```text
|
||||
GET /students
|
||||
```
|
||||
|
||||
填写:
|
||||
|
||||
```text
|
||||
page = 1
|
||||
page_size = 5
|
||||
```
|
||||
|
||||
其他查询条件暂时留空,然后执行。
|
||||
|
||||
预期结果:
|
||||
|
||||
- `code` 为 `200`;
|
||||
- `total` 表示未删除学生的总数;
|
||||
- `page` 为 `1`;
|
||||
- `page_size` 为 `5`;
|
||||
- `data` 最多返回 5 条;
|
||||
- 数据按照学生 ID 倒序排列,刚新增的学生通常在第一条。
|
||||
|
||||
从返回结果中找到 `student_name` 为“答辩演示学生”的记录,并记下:
|
||||
|
||||
```text
|
||||
id = ________
|
||||
student_no = ________
|
||||
```
|
||||
|
||||
后续所有 `{id}` 都替换成这里记录的真实学生 ID。
|
||||
|
||||
讲解词:
|
||||
|
||||
> 查询时先添加 is_deleted 等于 0 的条件,确保逻辑删除的数据不可见;然后在分页之前调用 count 获取符合条件的总数,最后使用 offset 和 limit 完成分页。固定按 ID 倒序可以保证分页顺序稳定。
|
||||
|
||||
分页计算公式:
|
||||
|
||||
```text
|
||||
offset = (page - 1) * page_size
|
||||
```
|
||||
|
||||
## 步骤 5:演示姓名模糊查询
|
||||
|
||||
继续使用:
|
||||
|
||||
```text
|
||||
GET /students
|
||||
```
|
||||
|
||||
填写:
|
||||
|
||||
```text
|
||||
student_name = 答辩
|
||||
page = 1
|
||||
page_size = 10
|
||||
```
|
||||
|
||||
预期结果:只返回姓名中包含“答辩”的未删除学生。
|
||||
|
||||
讲解词:
|
||||
|
||||
> 查询参数都是可选的。前端传入姓名时,DAO 使用 LIKE 完成模糊查询;没有传入的条件不会拼接到 SQL 中。
|
||||
|
||||
也可以补充展示以下任意一种查询:
|
||||
|
||||
```text
|
||||
student_no = 上一步记录的学号
|
||||
```
|
||||
|
||||
或者:
|
||||
|
||||
```text
|
||||
class_id = 1
|
||||
```
|
||||
|
||||
## 步骤 6:查询单个学生详情
|
||||
|
||||
展开:
|
||||
|
||||
```text
|
||||
GET /students/{id}
|
||||
```
|
||||
|
||||
将 `id` 填写为步骤 4 记录的学生 ID,然后执行。
|
||||
|
||||
预期响应结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"detail": "查询成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"student_no": "STU0001",
|
||||
"student_name": "答辩演示学生",
|
||||
"class_id": 1,
|
||||
"consultant_id": null,
|
||||
"native_place": "河南",
|
||||
"graduation_school": "演示大学",
|
||||
"major": "软件工程",
|
||||
"enrollment_date": "2025-09-01",
|
||||
"graduation_date": "2027-06-30",
|
||||
"education": "本科",
|
||||
"age": 22,
|
||||
"gender": "男"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
实际的 `id` 和 `student_no` 以数据库返回为准。
|
||||
|
||||
讲解词:
|
||||
|
||||
> DAO 使用主键和 is_deleted 等于 0 两个条件查询。API 层手动组装返回字典,只暴露业务需要的字段,不返回 is_deleted、创建时间等内部字段。
|
||||
|
||||
## 步骤 7:局部更新学生
|
||||
|
||||
展开:
|
||||
|
||||
```text
|
||||
POST /students/{id}
|
||||
```
|
||||
|
||||
将 `id` 填写为步骤 4 记录的学生 ID,请求体只输入需要修改的字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"age": 23,
|
||||
"gender": "女"
|
||||
}
|
||||
```
|
||||
|
||||
预期结果:返回“修改学生成功”,其他未传字段保持不变。
|
||||
|
||||
讲解词:
|
||||
|
||||
> 更新模型中的字段都是可选字段,接口通过 model_dump 的 exclude_unset 参数只提取前端实际传入的字段,DAO 再使用 setattr 循环完成局部更新。
|
||||
|
||||
更新完成后,再执行一次:
|
||||
|
||||
```text
|
||||
GET /students/{id}
|
||||
```
|
||||
|
||||
确认:
|
||||
|
||||
```text
|
||||
age = 23
|
||||
gender = 女
|
||||
```
|
||||
|
||||
其余字段没有变化。
|
||||
|
||||
## 步骤 8:演示更新参数校验
|
||||
|
||||
再次调用:
|
||||
|
||||
```text
|
||||
POST /students/{id}
|
||||
```
|
||||
|
||||
输入:
|
||||
|
||||
```json
|
||||
{
|
||||
"age": 200,
|
||||
"gender": "其他"
|
||||
}
|
||||
```
|
||||
|
||||
预期结果:HTTP 状态码为 `422`。
|
||||
|
||||
讲解词:
|
||||
|
||||
> 年龄限制在 0 到 150 之间,性别限制为男或女。参数校验失败时,请求不会进入更新 DAO,因此数据库中的数据不会被修改。
|
||||
|
||||
## 步骤 9:逻辑删除学生
|
||||
|
||||
展开:
|
||||
|
||||
```text
|
||||
DELETE /students/{id}
|
||||
```
|
||||
|
||||
填写步骤 4 记录的学生 ID,然后执行。
|
||||
|
||||
预期响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"detail": "删除学生成功"
|
||||
}
|
||||
```
|
||||
|
||||
讲解词:
|
||||
|
||||
> 删除操作不是物理删除,而是把 is_deleted 更新为 1。这样可以保留历史数据,同时所有正常查询都会通过 is_deleted 等于 0 将其过滤掉。
|
||||
|
||||
## 步骤 10:验证逻辑删除
|
||||
|
||||
删除后,再执行:
|
||||
|
||||
```text
|
||||
GET /students/{id}
|
||||
```
|
||||
|
||||
预期结果:HTTP 状态码为 `404`:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "学生不存在"
|
||||
}
|
||||
```
|
||||
|
||||
再执行学生列表查询或姓名模糊查询,也不应该看到刚才删除的学生。
|
||||
|
||||
讲解词:
|
||||
|
||||
> 数据库记录仍然存在,但详情查询和列表查询都统一增加了 is_deleted 等于 0 的条件,所以业务层已经无法查询到该记录。
|
||||
|
||||
## 三、建议的答辩讲解顺序
|
||||
|
||||
可以用下面这段话概括学生模块:
|
||||
|
||||
> 学生模块采用 API、Schema、DAO 和 Model 分层。Schema 负责请求参数的类型、范围和业务规则校验;API 负责接收请求、处理 HTTP 状态码以及控制响应字段;DAO 负责数据库查询和事务处理;Model 负责学生表与字段的 ORM 映射。列表接口支持学号精确查询、姓名模糊查询、班级查询以及分页,并统一过滤逻辑删除数据。新增支持自动生成学生编号,修改支持局部更新,删除采用逻辑删除。
|
||||
|
||||
## 四、常见提问与回答
|
||||
|
||||
### 1. 为什么分页之前先执行 count?
|
||||
|
||||
因为前端需要知道符合条件的总数据量。`count()` 必须在 `offset` 和 `limit` 之前执行,否则统计到的可能只是当前页的数据量。
|
||||
|
||||
### 2. 为什么分页查询要排序?
|
||||
|
||||
如果不指定排序,数据库每次返回数据的顺序不一定稳定,可能造成翻页时数据重复或遗漏。当前代码按照学生 ID 倒序排列。
|
||||
|
||||
### 3. 为什么使用逻辑删除?
|
||||
|
||||
逻辑删除能够保留历史数据,避免误删除后无法恢复。正常查询通过 `is_deleted == 0` 过滤已删除记录。
|
||||
|
||||
### 4. 为什么列表和详情要手动组装字典?
|
||||
|
||||
手动组装能够明确控制暴露给前端的字段,避免把 `is_deleted` 等数据库内部字段返回给用户。
|
||||
|
||||
### 5. 为什么修改接口只更新部分字段?
|
||||
|
||||
前端可能只修改年龄或手机号等个别字段。使用 `exclude_unset=True` 只获取实际传入的字段,再通过 `setattr` 更新,可以避免未传字段被错误覆盖。
|
||||
|
||||
### 6. 为什么非法参数返回 422?
|
||||
|
||||
FastAPI 会先调用 Pydantic 校验请求体。数据类型、字段范围或 Literal 约束不满足时,请求不会进入业务函数,而是直接返回 422。
|
||||
|
||||
### 7. 为什么学生编号可以不传?
|
||||
|
||||
学生记录执行 `flush()` 后可以取得数据库生成的自增主键,系统再根据主键生成学生编号,能够减少手工编号冲突。
|
||||
|
||||
## 五、故障排查
|
||||
|
||||
### 请求体报 JSON decode error
|
||||
|
||||
检查以下问题:
|
||||
|
||||
- 属性名和值必须使用英文双引号;
|
||||
- 最后一个字段后面不能有逗号;
|
||||
- JSON 中不能写注释;
|
||||
- 日期必须使用 `YYYY-MM-DD` 格式。
|
||||
|
||||
合法示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"age": 23,
|
||||
"gender": "女"
|
||||
}
|
||||
```
|
||||
|
||||
### 新增返回 500
|
||||
|
||||
优先检查:
|
||||
|
||||
1. MySQL 是否启动;
|
||||
2. 数据库连接配置是否正确;
|
||||
3. `class_id` 是否在班级表中真实存在;
|
||||
4. 手动填写的 `student_no` 是否与已有记录重复;
|
||||
5. 如果填写了 `consultant_id`,该顾问是否真实存在。
|
||||
|
||||
### 查询详情返回 404
|
||||
|
||||
检查:
|
||||
|
||||
1. ID 是否填写正确;
|
||||
2. 该学生是否已经被逻辑删除;
|
||||
3. 是否误用了列表中的班级 ID 或顾问 ID。
|
||||
|
||||
### Swagger 请求体只显示 `{}`
|
||||
|
||||
检查 `StudentUpdate` 是否被错误地重复嵌套。正确写法应该只有一层:
|
||||
|
||||
```python
|
||||
class StudentUpdate(BaseModel):
|
||||
student_name: str | None = None
|
||||
# 其他字段……
|
||||
```
|
||||
|
||||
## 六、演示结束检查表
|
||||
|
||||
- [ ] 服务可以正常启动;
|
||||
- [ ] Swagger 可以打开;
|
||||
- [ ] 已确认一个真实存在的班级 ID;
|
||||
- [ ] 性别非法数据返回 422;
|
||||
- [ ] 日期非法数据返回 422;
|
||||
- [ ] 合法学生新增成功;
|
||||
- [ ] 已从列表中记录新增学生 ID 和学号;
|
||||
- [ ] 分页和条件查询成功;
|
||||
- [ ] 详情查询不暴露内部字段;
|
||||
- [ ] 局部更新后未传字段保持不变;
|
||||
- [ ] 逻辑删除成功;
|
||||
- [ ] 删除后详情查询返回 404。
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import RedirectResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pathlib import Path
|
||||
from api.example import example_router
|
||||
from middleware import log_middleware
|
||||
from api.statistics import statistics_router
|
||||
@@ -8,6 +11,12 @@ from api.teacher import router as teacher_router,class_teacher_router
|
||||
from api.magic import magic_router
|
||||
|
||||
app = FastAPI(title='学生管理系统')
|
||||
app.mount("/ui", StaticFiles(directory=Path(__file__).resolve().parent / "frontend", html=True), name="frontend")
|
||||
|
||||
@app.get("/", include_in_schema=False)
|
||||
def home():
|
||||
return RedirectResponse("/ui/")
|
||||
|
||||
app.middleware("http")(log_middleware)
|
||||
app.include_router(magic_router)
|
||||
|
||||
@@ -25,7 +34,6 @@ from api import consultant
|
||||
app.include_router(consultant.router)
|
||||
#-------------张昕浩---------------
|
||||
app.include_router(example_router)
|
||||
app.include_router(magic_router)
|
||||
app.include_router(statistics_router, prefix="/statistics")
|
||||
|
||||
#-------------曾凯---------------
|
||||
|
||||
@@ -5,7 +5,7 @@ from core.database import Base # 从core软件包下的database模块导入B
|
||||
from sqlalchemy.dialects.mysql import TINYINT
|
||||
|
||||
class Classes( Base ): # 在python里的名字
|
||||
__tablename__ = 'class_info_detail' # 在数据库中表的名字
|
||||
__tablename__ = 'class_info_detail' # 在数据库中表的名字
|
||||
#————创建班级"主键"的字段名————
|
||||
id = Column( Integer # 声明字段的数据类型是"整数"
|
||||
, primary_key = True # 声明是"主键"
|
||||
|
||||
@@ -8,7 +8,7 @@ from datetime import datetime,date # 导入datetime模块下的datetime,da
|
||||
class ClassCreate( BaseModel ): # "创建"班级信息的请求体模型
|
||||
class_no: str # 班级编号,不可为空
|
||||
class_name: str # 班级名字,不可为空
|
||||
start_date : date|None = None # 班级开课日期,可以为空,默认为空
|
||||
start_date : date # 班级开课日期,不可为空
|
||||
end_date : date|None = None # 班级结课日期,可以为空,默认为空
|
||||
remark : str|None = None # 班级备注,可以为空,默认为空
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from pydantic import BaseModel, Field,ConfigDict#专门存放模型配置参数的字典类。把各种开关配置放里面。
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
+10
-1
@@ -1,4 +1,13 @@
|
||||
from pydantic import BaseModel,Field
|
||||
|
||||
class NoExist(Exception):
|
||||
def __init__(self,value):
|
||||
self.value = value
|
||||
pass
|
||||
pass
|
||||
|
||||
|
||||
class GenderCount(BaseModel):
|
||||
input_class:int|None = None
|
||||
|
||||
class StudentScore(BaseModel):
|
||||
score:int = Field(default = 80, ge = 0, le = 100)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from datetime import date
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from typing import Literal
|
||||
|
||||
@@ -40,3 +42,6 @@ class StudentUpdate(BaseModel):
|
||||
age: int | None = Field(default=None, ge=0, le=150)
|
||||
gender: Literal["男", "女"] | None = None
|
||||
|
||||
class Gender(str,Enum):
|
||||
g1 = '男'
|
||||
g2 = '女'
|
||||
Reference in New Issue
Block a user