Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e31970e696 | ||
|
|
9922f4fe48 | ||
|
|
7f58136a9f | ||
|
|
e76cee4bd6 | ||
|
|
ff68a676c4 | ||
|
|
ea9c85d7da | ||
|
|
336467b96a | ||
|
|
3b1a9d54a5 | ||
|
|
ba2155643b | ||
|
|
a571243415 | ||
|
|
dae4d74e31 | ||
|
|
ff03df7d8d | ||
|
|
8b50a337fa | ||
|
|
4604d3ba5f | ||
|
|
133cb530c7 | ||
|
|
98d08cff64 | ||
|
|
180e01e36d | ||
|
|
65bf147fd4 | ||
|
|
9c39c69fa1 | ||
|
|
b068ad6b29 | ||
|
|
9bc57bcbba | ||
|
|
d4d1d4fe2d | ||
|
|
c740efc022 | ||
|
|
5881afd5cc |
@@ -1,7 +0,0 @@
|
||||
# .env(放项目根目录,每个人自己建,不提交 Git)
|
||||
# 用来修改数据库账户密码
|
||||
DB_USER=root
|
||||
DB_PASSWORD=420821
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=student_manage_system
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
from fastapi import APIRouter , Depends , HTTPException
|
||||
from StatCalc.dao.statcalc_dao import *
|
||||
from StatCalc.model.statcalc_model import *
|
||||
from StatCalc.schema.statcalc_request import *
|
||||
from databases import *
|
||||
|
||||
statcalc_api = APIRouter()
|
||||
|
||||
@statcalc_api.get("/statcalc"
|
||||
@statcalc_api.get("/statcalc/age"
|
||||
,summary='人员信息查询'
|
||||
,description=f'查询所有超过30岁的学员的信息。'
|
||||
)
|
||||
@@ -18,95 +17,49 @@ def get_students(db=Depends(get_db)):
|
||||
return result
|
||||
raise HTTPException(status_code=404, detail="没有找到符合条件的学生")
|
||||
|
||||
@statcalc_api.get("/statcalc"
|
||||
@statcalc_api.get("/statcalc/students"
|
||||
,summary='人数统计'
|
||||
,description=f'统计每个班级的人数以及男生女生的人数。'
|
||||
)
|
||||
def get_students(g:get_student_dao = Depends()
|
||||
def get_students(headcount:get_student_dao
|
||||
,db=Depends(get_db)
|
||||
):
|
||||
result = get_class_count( g=g , db=db )
|
||||
result = get_class_count( g=headcount , db=db )
|
||||
if result:
|
||||
return result
|
||||
raise HTTPException(status_code=404, detail="没有找到符合条件的学生")
|
||||
|
||||
@statcalc_api.get("/statcalc"
|
||||
@statcalc_api.get("/statcalc/scores"
|
||||
,summary='80分以上成绩学生信息查询'
|
||||
,description=f'查询每次考试成绩都在80分以上的学生的编号,姓名和成绩。'
|
||||
)
|
||||
def get_students(db=Depends(get_db)):
|
||||
result = get_student_dao( db=db )
|
||||
result = get_score_80( db=db )
|
||||
if result:
|
||||
return result
|
||||
raise HTTPException(status_code=404, detail="没有找到符合条件的学生")
|
||||
|
||||
@statcalc_api.get("/statcalc"
|
||||
@statcalc_api.get("/statcalc/fail"
|
||||
,summary='不及格成绩查询'
|
||||
,description=f'查询有两次以上不及格的学生的姓名,班级和不及格成绩。'
|
||||
)
|
||||
def get_students( stu:StudentsQuery = Depends()
|
||||
, db=Depends(get_db)
|
||||
):
|
||||
s_dict = stu.model_dump(exclude_unset=True) # 没值的不要
|
||||
result = get_student_dao( s=s_dict , db=db )
|
||||
def get_students( db=Depends(get_db) ):
|
||||
result = get_score_80( db=db )
|
||||
if result:
|
||||
return result
|
||||
raise HTTPException(status_code=404, detail="没有找到符合条件的学生")
|
||||
|
||||
@statcalc_api.get("/statcalc"
|
||||
,summary='班级平均分查询'
|
||||
,description=f'统计每次考试每个班级的平均分,按照从高到低排序。'
|
||||
)
|
||||
def get_students( stu:StudentsQuery = Depends()
|
||||
, db=Depends(get_db)
|
||||
):
|
||||
s_dict = stu.model_dump(exclude_unset=True) # 没值的不要
|
||||
result = get_student_dao( s=s_dict , db=db )
|
||||
if result:
|
||||
return result
|
||||
raise HTTPException(status_code=404, detail="没有找到符合条件的学生")
|
||||
|
||||
@statcalc_api.get("/statcalc"
|
||||
,summary='排名前五的学生信息'
|
||||
,description=f'统计就业薪资最高的前五名学生的姓名,班级和就业时间,就业公司。'
|
||||
)
|
||||
def get_students( stu:StudentsQuery = Depends()
|
||||
, db=Depends(get_db)
|
||||
):
|
||||
s_dict = stu.model_dump(exclude_unset=True) # 没值的不要
|
||||
result = get_student_dao( s=s_dict , db=db )
|
||||
if result:
|
||||
return result
|
||||
raise HTTPException(status_code=404, detail="没有找到符合条件的学生")
|
||||
|
||||
@statcalc_api.get("/statcalc"
|
||||
,summary='就业时长统计'
|
||||
,description=f'统计每个学生的就业时长(offer下发时间-就业开放时间)。'
|
||||
)
|
||||
def get_students( stu:StudentsQuery = Depends()
|
||||
, db=Depends(get_db)
|
||||
):
|
||||
s_dict = stu.model_dump(exclude_unset=True) # 没值的不要
|
||||
result = get_student_dao( s=s_dict , db=db )
|
||||
if result:
|
||||
return result
|
||||
raise HTTPException(status_code=404, detail="没有找到符合条件的学生")
|
||||
|
||||
@statcalc_api.get("/statcalc"
|
||||
,summary='班级的平均就业时长'
|
||||
,description=f'统计每个班级的平均就业时长(只统计进入就业阶段的学生,也就是有就业开放时间)'
|
||||
)
|
||||
def get_students( stu:StudentsQuery = Depends()
|
||||
, db=Depends(get_db)
|
||||
):
|
||||
s_dict = stu.model_dump(exclude_unset=True) # 没值的不要
|
||||
result = get_student_dao( s=s_dict , db=db )
|
||||
if result:
|
||||
return result
|
||||
raise HTTPException(status_code=404, detail="没有找到符合条件的学生")
|
||||
@statcalc_api.get('/statcalc/average', summary='统计每次考试每个班级的平均分')
|
||||
def get_class_avg(db=Depends(get_db)):
|
||||
result = get_class_avg_dao(db)
|
||||
if not result:
|
||||
return []
|
||||
return result
|
||||
|
||||
|
||||
@CURD.get('/Top5',summary='就业薪资Top5')
|
||||
from stu_jiuye.model import *
|
||||
|
||||
@statcalc_api.get('/statcalc/top',summary='就业薪资Top5')
|
||||
def salarytop5(db=Depends(get_db)):
|
||||
q = db.query(Employments,Company.employment_company) \
|
||||
.join(Company, Employments.company_id == Company.id) \
|
||||
@@ -115,16 +68,19 @@ def salarytop5(db=Depends(get_db)):
|
||||
.limit(5) .all()
|
||||
return [{'学生姓名':i.sname,'学生班级编号':i.class_num,'就业时间':i.offer_recived_time,'公司':employment_company} for i,employment_company in q]
|
||||
|
||||
@CURD.get('/worktime',summary='学生就业时常')
|
||||
@statcalc_api.get('/statcalc/job',summary='学生就业时长')
|
||||
def worktime(db=Depends(get_db)):
|
||||
q = db.query(Employments)\
|
||||
.filter(Employments.is_deleted==0)\
|
||||
.all()
|
||||
|
||||
return [{'学生姓名':i.sname,'就业时常':f'{i.offer_recived_time-i.employment_open_time}' if i.offer_recived_time and i.employment_open_time else '暂无数据'} for i in q]
|
||||
return [
|
||||
{'学生姓名':i.sname
|
||||
,'就业时常':f'{i.offer_recived_time-i.employment_open_time}'
|
||||
if i.offer_recived_time and i.employment_open_time else '暂无数据'} for i in q
|
||||
]
|
||||
|
||||
|
||||
@CURD.get('/avgworktime',summary='学生平均就业时常')
|
||||
@statcalc_api.get('/statcalc/avgjob',summary='学生平均就业时长')
|
||||
def avgworktime(db=Depends(get_db)):
|
||||
q = db.query(Employments)\
|
||||
.filter(Employments.employment_open_time.isnot(None)
|
||||
@@ -139,3 +95,4 @@ def avgworktime(db=Depends(get_db)):
|
||||
'人数': len(q),
|
||||
'平均就业时常(天)': round(avg_days, 2)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,10 +39,38 @@ def get_class_count(g , db):
|
||||
from scores.model.score_model import Score
|
||||
|
||||
def get_score_80(db):
|
||||
q = (db.query(Score)
|
||||
.filter(Score.score>=80)
|
||||
.all())
|
||||
sq = ( db.query(Students.id)
|
||||
.join(Score, Score.sid == Students.id)
|
||||
.filer(Score.score < 80 )
|
||||
.group_by(Students.id)
|
||||
.having(func.count(Score.id) > 2 )
|
||||
.sq()
|
||||
)
|
||||
|
||||
q = ( db.query(Students.name,ClassInfo.name,Score.score )
|
||||
.join(ClassInfo, ClassInfo.id == Students.class_id)
|
||||
.join(Score, Score.sid == Students.id)
|
||||
.filer(Students.id.in_(sq),Score.score < 80)
|
||||
.all()
|
||||
)
|
||||
return q
|
||||
|
||||
def get_class_avg_dao(db):
|
||||
results = (db.query(Score.num,
|
||||
Score.cid,
|
||||
ClassInfo.name.label("class_name"),
|
||||
func.avg(Score.score).label("avg_score"))
|
||||
.join(ClassInfo, Score.cid == ClassInfo.id)
|
||||
.filter(Score.is_deleted == 0)
|
||||
.group_by(Score.num, Score.cid, ClassInfo.name)
|
||||
.order_by(func.avg(Score.score).desc())
|
||||
.all())
|
||||
|
||||
return [ {"num": r.num,
|
||||
"cid": r.cid,
|
||||
"class_name": r.class_name,
|
||||
"avg_score": round(float(r.avg_score), 2),}
|
||||
for r in results]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from fastapi import FastAPI,APIRouter
|
||||
from StatCalc.api.statcalc_api import statcalc_api
|
||||
from databases import *
|
||||
from StatCalc.model import statcalc_model
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
StatCalc_API = APIRouter()
|
||||
|
||||
@@ -4,7 +4,7 @@ from pydantic import BaseModel , Field , field_validator
|
||||
from datetime import datetime
|
||||
import enum
|
||||
|
||||
class get_student_dao(enum.Enum):
|
||||
class get_student_dao(str , enum.Enum):
|
||||
get_class = "班级人数"
|
||||
get_sex = "男女人数"
|
||||
get_class_sex = "班级中男女人数"
|
||||
|
||||
+3
-3
@@ -9,11 +9,11 @@ Teachers_API.include_router(tea_api)
|
||||
|
||||
# 自测接口
|
||||
app = FastAPI()
|
||||
app.include_router(tea_api,tags=["老师"])
|
||||
|
||||
Base.metadata.create_all(engine1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.include_router(tea_api, tags=["老师"])
|
||||
Base.metadata.create_all(engine1)
|
||||
|
||||
import uvicorn
|
||||
uvicorn.run('main:app', host='0.0.0.0', port=57000)
|
||||
"""
|
||||
|
||||
@@ -7,24 +7,52 @@ from class_management.schema.class_management_request import ClassCreate, ClassR
|
||||
class_api = APIRouter(prefix='/classes')
|
||||
# ,tags=['班级管理模块']
|
||||
|
||||
|
||||
#新增接口
|
||||
@class_api.post("",summary='新增班级')
|
||||
def add_class(cla:ClassCreate,db=Depends(get_db)):
|
||||
c_dict = cla.model_dump()
|
||||
r = add_class_dao(c=c_dict,db=db)
|
||||
if not r:
|
||||
raise HTTPException(status_code=500,detail='服务器繁忙,请稍后添加!')
|
||||
return ClassResponse(totals=1,data=c_dict)
|
||||
res = add_class_dao(c=c_dict,db=db)
|
||||
if not res:
|
||||
raise HTTPException(status_code=400,detail='新增失败,班级已存在')
|
||||
return ClassResponse(detail='新增成功',totals=1,data=c_dict)
|
||||
|
||||
|
||||
@class_api.get('/{id}',summary='根据id查询班级')
|
||||
def get_one_class(id:int,db=Depends(get_db)):
|
||||
res = get_class_dao(c={'id':id},db=db)
|
||||
#更新接口
|
||||
@class_api.put("/{class_id}",summary='修改班级信息')
|
||||
def update_class(class_id:int
|
||||
,update_info:ClassUpdate
|
||||
,db=Depends(get_db)
|
||||
):
|
||||
update_dict=update_info.model_dump(exclude_unset=True)
|
||||
if not update_dict:
|
||||
raise HTTPException(status_code=400, detail="没有传入要修改的字段")
|
||||
row_count = update_class_dao(cid=class_id, update_data=update_dict, db=db)
|
||||
if not row_count :
|
||||
raise HTTPException(status_code=404, detail="班级不存在,修改失败")
|
||||
return ClassResponse(detail="修改成功", data={"id": class_id})
|
||||
|
||||
|
||||
#删除接口
|
||||
@class_api.delete('/{class_id}',summary='逻辑删除班级')
|
||||
def remove_class(class_id:int,db=Depends(get_db)):
|
||||
del_rows = delete_class_dao(cid = class_id,db=db)
|
||||
if not del_rows:
|
||||
raise HTTPException(status_code=404, detail="班级不存在,删除失败")
|
||||
return ClassResponse(detail='删除成功',data={'id':class_id})
|
||||
|
||||
|
||||
|
||||
#单条查询接口
|
||||
@class_api.get('/{class_id}',summary='根据id查询班级')
|
||||
def get_one_class(class_id:int,db=Depends(get_db)):
|
||||
res = get_class_dao(c={'id':class_id},db=db)
|
||||
if not res:
|
||||
raise HTTPException(status_code=404, detail="该班级不存在或已删除")
|
||||
return ClassResponse(totals=1,data=res[0])
|
||||
|
||||
@class_api.get('',summary='多字段查询班级')
|
||||
|
||||
#分类查询
|
||||
@class_api.get('',summary='多字段分页查询班级')
|
||||
def get_some_classes( num:str|None = Query(None,description='班级编号')
|
||||
,name:str|None = Query(None,description='班级名称')
|
||||
,head_teacher_id:int|None = Query(None,description='班主任')
|
||||
@@ -33,10 +61,9 @@ def get_some_classes( num:str|None = Query(None,description='班级编号')
|
||||
,class_start_time:date|None = Query(None, description="开班日期,格式YYYY‑MM‑DD")
|
||||
,class_end_time: date|None = Query(None, description="结课日期,格式YYYY‑MM‑DD")
|
||||
,page:int = Query(1,ge=1,description='页码,从1开始')
|
||||
,page_size:int = Query(5,ge=1,le=50,description='每页信息条数')
|
||||
,page_size:int = Query(5,ge=1,le=20,description='每页信息条数')
|
||||
,db=Depends(get_db)
|
||||
):
|
||||
|
||||
c_dict = {
|
||||
'num': num,
|
||||
'name': name,
|
||||
@@ -52,7 +79,7 @@ def get_some_classes( num:str|None = Query(None,description='班级编号')
|
||||
res = get_class_dao(c=c_dict,db=db)
|
||||
total = len(res)
|
||||
|
||||
#内存分页
|
||||
#分页
|
||||
start_index = (page-1)*page_size
|
||||
page_data = res[start_index:start_index + page_size]
|
||||
|
||||
@@ -61,23 +88,3 @@ def get_some_classes( num:str|None = Query(None,description='班级编号')
|
||||
|
||||
|
||||
|
||||
@class_api.put("/{class_id}",summary='修改班级信息')
|
||||
def update_class(class_id:int
|
||||
,update_info:ClassUpdate
|
||||
,db=Depends(get_db)
|
||||
):
|
||||
update_dict=update_info.model_dump(exclude_unset=True)
|
||||
if not update_dict:
|
||||
raise HTTPException(status_code=400, detail="没有传入要修改的字段")
|
||||
row_count = update_class_dao(cid=class_id, update_data=update_dict, db=db)
|
||||
if row_count <= 0:
|
||||
raise HTTPException(status_code=404, detail="班级不存在,修改失败")
|
||||
return ClassResponse(detail="修改成功", data={"class_id": id})
|
||||
|
||||
|
||||
@class_api.delete('/{id}',summary='逻辑删除班级')
|
||||
def remove_class(id:int,db=Depends(get_db)):
|
||||
del_rows = delete_class_dao(id,db)
|
||||
if del_rows <= 0:
|
||||
raise HTTPException(status_code=404, detail="班级不存在,删除失败")
|
||||
return ClassResponse(detail='删除成功',data={'id':id})
|
||||
|
||||
@@ -1,6 +1,53 @@
|
||||
from class_management.model.class_management_model import ClassInfo
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
#新增班级
|
||||
def add_class_dao(c,db):
|
||||
try:
|
||||
exist_class = db.query(ClassInfo).filter(
|
||||
ClassInfo.num == c["num"],
|
||||
ClassInfo.is_delete == 0
|
||||
).first()
|
||||
if exist_class:
|
||||
return False
|
||||
c1 = ClassInfo(**c)
|
||||
db.add(c1)
|
||||
except:
|
||||
db.rollback()
|
||||
return False
|
||||
else:
|
||||
db.commit()
|
||||
db.refresh(c1)
|
||||
return True
|
||||
|
||||
|
||||
#更新班级信息
|
||||
def update_class_dao(cid,update_data,db):
|
||||
try:
|
||||
rows = db.query(ClassInfo)\
|
||||
.filter(ClassInfo.id == cid
|
||||
,ClassInfo.is_delete==0)\
|
||||
.update(update_data)
|
||||
except :
|
||||
db.rollback()
|
||||
return False
|
||||
else:
|
||||
db.commit()
|
||||
return rows
|
||||
|
||||
|
||||
#删除(逻辑删除)
|
||||
def delete_class_dao(cid,db):
|
||||
try:
|
||||
rows = db.query(ClassInfo)\
|
||||
.filter(ClassInfo.id == cid,ClassInfo.is_delete==0)\
|
||||
.update({'is_delete':1})
|
||||
db.commit()
|
||||
return rows
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print('删除失败',e)
|
||||
return 0
|
||||
|
||||
|
||||
#查询
|
||||
@@ -34,64 +81,6 @@ def get_class_dao(c:dict,db:Session) :
|
||||
,'tutor_teacher_id':i.tutor_teacher_id
|
||||
,'class_start_time':i.class_start_time
|
||||
,'class_end_time':i.class_end_time
|
||||
,"is_delete": i.is_delete
|
||||
,"create_time": i.create_time
|
||||
,"update_time": i.update_time
|
||||
} for i in res
|
||||
]
|
||||
|
||||
|
||||
#新增班级
|
||||
def add_class_dao(c:dict,db:Session):
|
||||
try:
|
||||
exist_class = db.query(ClassInfo)\
|
||||
.filter(ClassInfo.name == c['name']
|
||||
,ClassInfo.is_delete == 0)\
|
||||
.first()
|
||||
if exist_class:
|
||||
msg = '班级名称已存在'
|
||||
print(msg)
|
||||
return (False,msg)
|
||||
c1 = ClassInfo(**c)
|
||||
db.add(c1)
|
||||
db.commit()
|
||||
db.refresh(c1)
|
||||
return (True,None)
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
err_msg = f'数据库异常:{str(e)}'
|
||||
print(err_msg)
|
||||
return (False,err_msg)
|
||||
|
||||
|
||||
#修改
|
||||
def update_class_dao(cid:int,update_data,db):
|
||||
try:
|
||||
rows = db.query(ClassInfo)\
|
||||
.filter(ClassInfo.id == cid
|
||||
,ClassInfo.is_delete==0)\
|
||||
.update(update_data)
|
||||
db.commit()
|
||||
return rows
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"update_class_dao异常:{e}")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
#删除(逻辑删除)
|
||||
def delete_class_dao(cid,db):
|
||||
try:
|
||||
rows = db.query(ClassInfo)\
|
||||
.filter(ClassInfo.id == cid,ClassInfo.is_delete==0)\
|
||||
.update({'is_delete':1})
|
||||
db.commit()
|
||||
return rows
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"delete_class_dao异常:{e}")
|
||||
rows = 0
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ SessionLocal = sessionmaker(bind=engine,autocommit=False, autoflush=False)
|
||||
Base = declarative_base()
|
||||
|
||||
# 获取数据库会话依赖
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try :
|
||||
|
||||
@@ -7,14 +7,12 @@ from class_management.database import Base,engine
|
||||
Classes_API = APIRouter()
|
||||
Classes_API.include_router(class_api)
|
||||
|
||||
# 自测接口
|
||||
app = FastAPI()
|
||||
|
||||
app.include_router(class_api)
|
||||
|
||||
|
||||
|
||||
if __name__ =='__main__':
|
||||
# 自测接口
|
||||
app = FastAPI()
|
||||
|
||||
app.include_router(class_api)
|
||||
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
import uvicorn
|
||||
|
||||
@@ -14,6 +14,7 @@ class ClassInfo(Base_all):
|
||||
)
|
||||
num = Column(String(50)
|
||||
,nullable=False
|
||||
,unique=True
|
||||
,comment='班级编号'
|
||||
)
|
||||
name = Column(String(50)
|
||||
@@ -21,26 +22,18 @@ class ClassInfo(Base_all):
|
||||
,comment='班级名称'
|
||||
)
|
||||
head_teacher_id = Column(Integer
|
||||
# ,ForeignKey('teacher.id')
|
||||
,ForeignKey('teachers.id')
|
||||
,comment='班主任编号'
|
||||
)
|
||||
|
||||
coach_teacher_id = Column(Integer
|
||||
# ,ForeignKey('teacher.id')
|
||||
,ForeignKey('teachers.id')
|
||||
,comment='授课老师编号'
|
||||
)
|
||||
tutor_teacher_id = Column(Integer
|
||||
# ,ForeignKey('teacher.id')
|
||||
,ForeignKey('teachers.id')
|
||||
,comment='助教老师编号'
|
||||
)
|
||||
# class_teacher = Column(String(50)
|
||||
# ,nullable=False
|
||||
# ,comment='班主任'
|
||||
# )
|
||||
# class_lecturer = Column(String(50)
|
||||
# ,nullable=False
|
||||
# ,comment='授课老师'
|
||||
# )
|
||||
class_start_time = Column(Date
|
||||
,comment='开班日期'
|
||||
)
|
||||
|
||||
@@ -12,17 +12,14 @@ class ClassBase(BaseModel):
|
||||
class_start_time:date|None = Field(None,description='开班日期 YYYY‑MM‑DD')
|
||||
class_end_time:date|None = Field(None,description='结课日期 YYYY‑MM‑DD')
|
||||
|
||||
#新增班级请求体:num,name必填,其余继承父类ClassBase
|
||||
#新增班级请求体:继承父类ClassBase
|
||||
class ClassCreate(ClassBase):
|
||||
num:str = Field(...,description='班级编号')
|
||||
name:str = Field(...,description='班级名称')
|
||||
pass
|
||||
|
||||
#修改班级请求体,继承ClassBase,全部字段可选,传哪个就更新哪个
|
||||
class ClassUpdate(ClassBase):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ClassResponse(BaseModel):
|
||||
code:int = 200
|
||||
detail:str = 'OK'
|
||||
|
||||
@@ -4,22 +4,22 @@ from scores.main import Scores_API
|
||||
from stu_jiuye.main import Shtudent_Jiuye
|
||||
from students.main import Students_API
|
||||
from Teachers.main import Teachers_API
|
||||
# from StatCalc.main import StatCalc_API
|
||||
from StatCalc.main import StatCalc_API
|
||||
from databases import *
|
||||
app = FastAPI(title="学生管理系统")
|
||||
|
||||
|
||||
app.include_router(Classes_API,tags=["classes"])
|
||||
app.include_router(Scores_API,tags=["scores"])
|
||||
app.include_router(Shtudent_Jiuye,tags=["Employments"])
|
||||
app.include_router(Students_API,tags=["students"])
|
||||
app.include_router(Teachers_API,tags=["teachers"])
|
||||
# app.include_router(StatCalc_API,tags=["StatCalc"])
|
||||
app.include_router(Classes_API,tags=["班级管理模块"])
|
||||
app.include_router(Scores_API,tags=["学生考核成绩管理模块"])
|
||||
app.include_router(Shtudent_Jiuye,tags=["学生就业管理模块"])
|
||||
app.include_router(Students_API,tags=["学生管理模块"])
|
||||
app.include_router(Teachers_API,tags=["老师管理模块"])
|
||||
app.include_router(StatCalc_API,tags=["统计分析模块"])
|
||||
|
||||
|
||||
Base_all.metadata.create_all(bind=engine_all)
|
||||
|
||||
if __name__ == "__main__":
|
||||
Base_all.metadata.create_all(bind=engine_all)
|
||||
|
||||
def main():
|
||||
print("Hello from student-manage-system!")
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ def update_scores(score:ScoreRequest,sid:int,cid:int,num:int,t_subject:int,db=De
|
||||
@score_api.delete('/Score',summary='成绩删除',description=f'查询条件为学号,班级号,考试科目,然后进行删除')
|
||||
def delete_scores(sid:int,cid:int,t_subject:int,db=Depends(get_db)):
|
||||
aaa = delete_scores_dao(sid,cid,t_subject,db)
|
||||
if aaa==1:
|
||||
if aaa==-1:
|
||||
raise HTTPException(status_code=500,detail='删除失败')
|
||||
if aaa==0:
|
||||
raise HTTPException(status_code=404,detail='记录不存在')
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from scores.database import get_db
|
||||
from scores.dao.statistic_dao import get_top_students_dao,get_failing_students_dao,get_class_avg_dao
|
||||
|
||||
statistic_api = APIRouter()
|
||||
|
||||
@statistic_api.get('/statistic', summary='查询每次考试都在80分以上的学生')
|
||||
def get_top_students(db=Depends(get_db)):
|
||||
q = get_top_students_dao(db)
|
||||
if not q:
|
||||
return []
|
||||
return q
|
||||
|
||||
@statistic_api.get('/Score/failing', summary='查询两次以上不及格的学生')
|
||||
def get_failing_students(db=Depends(get_db)):
|
||||
result = get_failing_students_dao(db)
|
||||
if not result:
|
||||
return []
|
||||
return result
|
||||
|
||||
@statistic_api.get('/Score/class-avg', summary='统计每次考试每个班级的平均分')
|
||||
def get_class_avg(db=Depends(get_db)):
|
||||
result = get_class_avg_dao(db)
|
||||
if not result:
|
||||
return []
|
||||
return result
|
||||
@@ -19,7 +19,7 @@ def get_scores_dao(s,db):
|
||||
if aa:
|
||||
return [{'id':i.id,'sid':i.sid
|
||||
,'cid':i.cid,'num':i.num
|
||||
,'score': i.score,'t_subject':i.tsub
|
||||
,'score': i.score,'t_subject':i.t_subject
|
||||
,'create_date':i.create_date,'update_date':i.update_date
|
||||
,'is_deleted': i.is_deleted,'deleted_date': i.deleted_date
|
||||
}for i in aa]
|
||||
@@ -28,8 +28,7 @@ def get_scores_dao(s,db):
|
||||
|
||||
def add_scores_dao(b,db):
|
||||
try:
|
||||
aa = db.query(Score).filter(Score.sid==b['sid']
|
||||
,Score.t_subject==b['t_subject']).all()
|
||||
aa = db.query(Score).filter(Score.t_subject==b['t_subject']).all()
|
||||
if aa:
|
||||
raise ValueError
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
from sqlalchemy import func
|
||||
from scores.model.score_model import Score
|
||||
from students.model.students_model import Students
|
||||
from class_management.model.class_management_model import ClassInfo
|
||||
|
||||
def get_top_students_dao(db):
|
||||
top_sids = (db.query(Score.sid)
|
||||
.filter(Score.is_deleted == 0)
|
||||
.group_by(Score.sid, Score.num)
|
||||
.having(func.min(Score.score) >= 80)
|
||||
.subquery() )
|
||||
|
||||
results = (db.query(Students.id,
|
||||
Students.name,
|
||||
Score.num,
|
||||
Score.t_subject,
|
||||
Score.score,)
|
||||
.join(Score, Students.id == Score.sid)
|
||||
.filter( Score.sid.in_(top_sids),
|
||||
Score.is_deleted == 0,)
|
||||
.order_by(Students.id, Score.num)
|
||||
.all())
|
||||
|
||||
return [{"sid": r.sid,
|
||||
"name": r.name,
|
||||
"score": r.score,}
|
||||
for r in results]
|
||||
|
||||
|
||||
def get_failing_students_dao(db):
|
||||
failing_sids = (db.query(Score.sid)
|
||||
.filter(Score.score < 60,
|
||||
Score.is_deleted == 0,)
|
||||
.group_by(Score.sid)
|
||||
.having(func.count() >= 2)
|
||||
.subquery())
|
||||
|
||||
results = (db.query(
|
||||
Students.id,
|
||||
Students.name,
|
||||
ClassInfo.name.label("class_name"),
|
||||
Score.num,
|
||||
Score.t_subject,
|
||||
Score.score,)
|
||||
.join(Score, Students.id == Score.sid)
|
||||
.join(ClassInfo, Score.cid == ClassInfo.id)
|
||||
.filter(Score.sid.in_(failing_sids),
|
||||
Score.score < 60,
|
||||
Score.is_deleted == 0,)
|
||||
.order_by(ClassInfo.id, Score.num)
|
||||
.all())
|
||||
|
||||
return [{"sid": r.sid,
|
||||
"name": r.name,
|
||||
"class_name": r.class_name,
|
||||
"num": r.num,
|
||||
"t_subject": r.tsub,
|
||||
"score": r.score,}
|
||||
for r in results]
|
||||
|
||||
|
||||
|
||||
def get_class_avg_dao(db):
|
||||
results = (db.query(Score.num,
|
||||
Score.cid,
|
||||
ClassInfo.name.label("class_name"),
|
||||
func.avg(Score.score).label("avg_score"))
|
||||
.join(ClassInfo, Score.cid == ClassInfo.id)
|
||||
.filter(Score.is_deleted == 0)
|
||||
.group_by(Score.num, Score.cid, ClassInfo.name)
|
||||
.order_by(func.avg(Score.score).desc())
|
||||
.all())
|
||||
|
||||
return [ {"num": r.num,
|
||||
"cid": r.cid,
|
||||
"class_name": r.class_name,
|
||||
"avg_score": round(float(r.avg_score), 2),}
|
||||
for r in results]
|
||||
+7
-3
@@ -1,17 +1,21 @@
|
||||
from fastapi import FastAPI,APIRouter
|
||||
from scores.api.score_api import score_api
|
||||
from scores.database import engine,Base
|
||||
from scores.api.statistic_api import statistic_api
|
||||
|
||||
# 合并接口
|
||||
Scores_API = APIRouter()
|
||||
Scores_API.include_router(score_api)
|
||||
|
||||
# 自测接口
|
||||
app = FastAPI(tags=['成绩'],title='成绩管理系统')
|
||||
app = FastAPI(tags=['成绩'], title='成绩管理系统')
|
||||
app.include_router(score_api)
|
||||
app.include_router(statistic_api)
|
||||
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
import uvicorn
|
||||
uvicorn.run('main:app',host='127.0.0.1',port=12346)
|
||||
uvicorn.run('scores.main:app',host='127.0.0.1',port=12346)
|
||||
@@ -93,7 +93,7 @@ def delete_stuinfo( #删除模块
|
||||
): #根据id选择对象
|
||||
rows = delete_emp_dao(id, db) #调用删除函数
|
||||
if not rows:
|
||||
raise HTTPException(status_code=500, detail='没有删除!') #失败返回错误信息
|
||||
raise HTTPException(status_code=404, detail='未找到该就业记录!') #失败返回错误信息
|
||||
return {'code': 200, 'totals': rows, 'detail': '删除成功'} #成功返回信息
|
||||
|
||||
'''
|
||||
|
||||
+13
-13
@@ -18,6 +18,7 @@ def add_employment_dao(o,db): #新增就业信息函数
|
||||
return False
|
||||
else:
|
||||
db.commit() #提交事务,永久保存
|
||||
db.refresh(o1)
|
||||
return True
|
||||
|
||||
def add_company_dao(o,db): #添加公司信息函数
|
||||
@@ -34,6 +35,7 @@ def add_company_dao(o,db): #添加公司信息函数
|
||||
return False
|
||||
else:
|
||||
db.commit() #提交事务,永久保存
|
||||
db.refresh(o1)
|
||||
return True
|
||||
def add_address_dao(o,db): #添加地址区域信息函数
|
||||
try:
|
||||
@@ -49,6 +51,7 @@ def add_address_dao(o,db): #添加地址区域信息函数
|
||||
return False
|
||||
else:
|
||||
db.commit() #提交事务,永久保存
|
||||
db.refresh(o1)
|
||||
return True
|
||||
|
||||
|
||||
@@ -82,24 +85,23 @@ def get_emp_dao( #获取学生
|
||||
# ,page
|
||||
# ,page_size
|
||||
): #根据选择条件获取信息
|
||||
q = db.query(Employments,Company.employment_company,Address.somewhere) \
|
||||
.outerjoin(Company, Students.id == Employments.stuid) \
|
||||
.outerjoin(Company, ClassInfo.id == Employments.class_id)\
|
||||
.outerjoin(Company,Employments.company_id==Company.id)\
|
||||
.outerjoin(Address,Company.address_id==Address.id)\
|
||||
.filter(Employments.is_deleted == 0) #连接公司表和地址表,筛选未被删除信息
|
||||
q = (db.query(Employments,Company.employment_company,Address.somewhere)
|
||||
.outerjoin(Students, Students.id == Employments.stuid)
|
||||
.outerjoin(ClassInfo, ClassInfo.id == Employments.class_id)
|
||||
.outerjoin(Company,Employments.company_id==Company.id)
|
||||
.outerjoin(Address,Employments.address_id==Address.id)
|
||||
.filter(Employments.is_deleted == 0)) #连接公司表和地址表,筛选未被删除信息
|
||||
# if stuid:
|
||||
# q = q.filter( Employments.id == stuid )
|
||||
|
||||
if sname: #根据学生姓名筛选
|
||||
q = q.filter( Students.name == sname )
|
||||
if class_id:
|
||||
q = q.filter(ClassInfo.id == class_id)
|
||||
q = q.filter(Employments.class_id == class_id)
|
||||
if somewhere:
|
||||
q = q.filter(Address.somewhere.like(f'%{somewhere}%'))
|
||||
if employment_company: #根据公司姓名筛选
|
||||
q=q.filter(Company.employment_company.like(f'%{employment_company}%'))
|
||||
# r = q.offset( (page-1)*page_size ).limit( page_size ).all()
|
||||
if min_salary is not None: #根据最小薪资筛选
|
||||
q = q.filter(Employments.employment_salary >= min_salary)
|
||||
if max_salary is not None: ##根据最大薪资筛选
|
||||
@@ -116,20 +118,18 @@ def get_emp_dao( #获取学生
|
||||
r= q.all()
|
||||
if r:
|
||||
return [ {"id": i.id,
|
||||
'学生姓名':i.sname,
|
||||
'学生姓名':sname,
|
||||
'学生班级编号':i.class_id,
|
||||
"公司": employment_company or '未就业',
|
||||
"公司区域":somewhere or '-',
|
||||
# "company_id": i.company_id,
|
||||
"薪资":i.employment_salary or '-',
|
||||
# "address_id": i.address_id,
|
||||
"就业开放时间":i.employment_open_time.strftime('%Y-%m-%d') ,
|
||||
"offer下发时间":i.offer_recived_time.strftime('%Y-%m-%d')
|
||||
# , "创建时间": i.create_time,
|
||||
# "更新时间": i.update_time,
|
||||
# "是否删除":i.is_deleted
|
||||
} for i,sname,class_id,employment_company,somewhere in r ] #返回信息列表
|
||||
|
||||
} for i,sname,employment_company,somewhere in r ] #返回信息列表
|
||||
return {"code": 200, "totals": len(r), "data": r}
|
||||
|
||||
def delete_emp_dao(id,db): #删除函数
|
||||
try:
|
||||
|
||||
+3
-6
@@ -8,15 +8,12 @@ Shtudent_Jiuye = APIRouter()
|
||||
Shtudent_Jiuye.include_router(CURD)
|
||||
|
||||
# 自测接口
|
||||
|
||||
|
||||
app=FastAPI()
|
||||
Base.metadata.create_all(engine)
|
||||
app.include_router(CURD,tags=['就业管理系统'])
|
||||
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
app.include_router(CURD, tags=['就业管理系统'])
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
import uvicorn
|
||||
uvicorn.run('main:app', host='127.0.0.1', port=12395)
|
||||
|
||||
|
||||
+7
-7
@@ -19,12 +19,14 @@ class Employments(Base_all): #学生
|
||||
class_id = Column(Integer
|
||||
, ForeignKey('classes.id')
|
||||
) #学生班级,冗余字段
|
||||
company_id = Column(Integer #公司id,外键匹配公司表
|
||||
company_id = Column(Integer # 公司id,外键匹配公司表
|
||||
, ForeignKey('Company.id')
|
||||
, nullable=True
|
||||
|
||||
)
|
||||
|
||||
address_id = Column(Integer # 地址id,匹配地址表
|
||||
, ForeignKey('Address.id') # 外键地址表id
|
||||
, nullable=False # 非空约束
|
||||
)
|
||||
employment_salary = Column(Integer, nullable=True) #薪资
|
||||
|
||||
employment_open_time = Column(DATE, nullable=True) #就业开放时间
|
||||
@@ -49,10 +51,7 @@ class Company(Base_all): #公司
|
||||
employment_company = Column(String(50) #公司姓名
|
||||
, nullable=False #非空约束
|
||||
)
|
||||
address_id = Column(Integer #地址id,匹配地址表
|
||||
, ForeignKey('Address.id') #外键地址表id
|
||||
, nullable=False #非空约束
|
||||
)
|
||||
|
||||
create_time = Column(DATETIME
|
||||
, default=datetime.now
|
||||
)
|
||||
@@ -71,6 +70,7 @@ class Address(Base_all):
|
||||
somewhere=Column(String(50) #地址区域
|
||||
, nullable=False
|
||||
)
|
||||
|
||||
create_time = Column(DATETIME
|
||||
, default=datetime.now
|
||||
)
|
||||
|
||||
@@ -4,10 +4,10 @@ from pydantic import BaseModel
|
||||
|
||||
class PostRequest(BaseModel): #学生就业信息请求体模型
|
||||
# compname: str|None=None
|
||||
sname: str|None=None
|
||||
stuid: int|None=None
|
||||
class_id: int|None=None
|
||||
company_id:int|None=None
|
||||
# address_id: int|None=None
|
||||
address_id: int|None=None
|
||||
employment_salary: int|None=None
|
||||
employment_open_time:date|None=None
|
||||
offer_recived_time:date|None=None
|
||||
@@ -21,7 +21,7 @@ class EmpResponse(BaseModel): #学生就
|
||||
|
||||
class CompRequest(BaseModel): #公司信息请求体模型
|
||||
employment_company:str
|
||||
address_id: int
|
||||
# address_id: int
|
||||
|
||||
class CompResponse(BaseModel): #公司信息响应体
|
||||
code: int = 200
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
# 学生就业信息管理系统 —— 就业模块项目文档
|
||||
|
||||
## 一、模块概述
|
||||
|
||||
本模块是「学生就业信息管理系统」的核心业务模块,负责管理学生就业状态的全生命周期。围绕就业信息(Employments)、公司信息(Company)、地址信息(Address)三张核心表,并与学生表(students)、班级表(classes)建立外键关联,提供增删改查(CRUD)与统计分析接口。
|
||||
|
||||
**技术栈:**
|
||||
|
||||
|
||||
|
||||
| 项 | 选型 |
|
||||
| ------ | --------------------- |
|
||||
| Web 框架 | FastAPI |
|
||||
| ORM | SQLAlchemy 2.x |
|
||||
| 数据库 | MySQL 8.x(驱动 PyMySQL) |
|
||||
| 数据校验 | Pydantic v2 |
|
||||
| 服务启动 | Uvicorn |
|
||||
|
||||
## 二、目录结构
|
||||
|
||||
|
||||
|
||||
```
|
||||
student\_manage\_system/
|
||||
|
||||
├── main.py # 程序入口:注册全模块路由、建表(\_\_main\_\_ 中执行)
|
||||
|
||||
├── databases.py # 根数据库配置:engine\_all / Base\_all / Session / get\_db
|
||||
|
||||
└── stu\_jiuye/ # 就业模块业务包
|
||||
|
||||
  ├── main.py # 路由组装(Shtudent\_Jiuye);独立自测 app
|
||||
|
||||
  ├── database.py # 模块级数据库配置(engine / Base / Session / get\_db)
|
||||
|
||||
  ├── model.py # ORM 模型:Employments / Company / Address
|
||||
|
||||
  ├── request.py # Pydantic 请求体 / 响应体模型
|
||||
|
||||
  ├── dao.py # 数据访问层(纯数据库操作)
|
||||
|
||||
  └── CURD\_api.py # 路由层(API 接口定义)
|
||||
```
|
||||
|
||||
**分层职责:**
|
||||
|
||||
|
||||
|
||||
| 层 | 文件 | 职责 |
|
||||
| ----- | ------------ | --------------------------- |
|
||||
| 路由层 | CURD\_api.py | 接收 HTTP 请求,参数校验,调用 DAO,返回响应 |
|
||||
| 数据访问层 | dao.py | 封装 SQLAlchemy 查询与事务,向上提供函数 |
|
||||
| 模型层 | model.py | 定义表结构与外键关系 |
|
||||
| 校验层 | request.py | 定义请求体 / 响应体的字段与类型 |
|
||||
| 配置层 | database.py | 数据库 URL、引擎、Session 工厂 |
|
||||
|
||||
## 三、数据表设计
|
||||
|
||||
### 1. Employments(就业信息表)
|
||||
|
||||
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
| ---------------------- | -------- | ------------------------- | ------------------ |
|
||||
| id | Integer | PK, 自增 | 主键 |
|
||||
| stuid | Integer | FK → students.id | 学生 ID |
|
||||
| class\_id | Integer | FK → classes.id | 班级 ID |
|
||||
| company\_id | Integer | FK → Company.id, 可空 | 就业公司 |
|
||||
| address\_id | Integer | FK → Address.id, NOT NULL | 就业地址 |
|
||||
| employment\_salary | Integer | 可空 | 就业薪资 |
|
||||
| employment\_open\_time | DATE | 可空 | 就业开放时间 |
|
||||
| offer\_recived\_time | DATE | 可空 | Offer 下发时间 |
|
||||
| create\_time | DateTime | default=now | 创建时间 |
|
||||
| update\_time | DateTime | default=now, onupdate=now | 更新时间 |
|
||||
| is\_deleted | Integer | default=0 | 软删除标记(0 正常 / 1 已删) |
|
||||
|
||||
### 2. Company(公司表)
|
||||
|
||||
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
| ------------------- | ---------- | ------------------------- | ----- |
|
||||
| id | Integer | PK, 自增 | 主键 |
|
||||
| employment\_company | String(50) | NOT NULL | 公司名称 |
|
||||
| create\_time | DateTime | default=now | 创建时间 |
|
||||
| update\_time | DateTime | default=now, onupdate=now | 更新时间 |
|
||||
| is\_deleted | Integer | default=0 | 软删除标记 |
|
||||
|
||||
### 3. Address(地址表)
|
||||
|
||||
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
| ------------ | ---------- | ------------------------- | --------------- |
|
||||
| id | Integer | PK, 自增 | 主键 |
|
||||
| somewhere | String(50) | NOT NULL | 地址区域,如 "深圳市南山区" |
|
||||
| create\_time | DateTime | default=now | 创建时间 |
|
||||
| update\_time | DateTime | default=now, onupdate=now | 更新时间 |
|
||||
| is\_deleted | Integer | default=0 | 软删除标记 |
|
||||
|
||||
### 表关系图
|
||||
|
||||
|
||||
|
||||
```
|
||||
students (1) ───< (N) Employments (N) >─── (1) Company
|
||||
|
||||
classes (1) ───< (N) | (N) >─── (1) Address
|
||||
```
|
||||
|
||||
|
||||
|
||||
* 一个学生可有多个就业记录,一条就业记录对应一个学生(通过 stuid 关联学生表)
|
||||
|
||||
* 一条就业记录对应一个公司(company\_id)与一个地址(address\_id)
|
||||
|
||||
* 通过多表 JOIN 可从就业记录直接获取学生姓名、班级名称、公司名称与地址信息
|
||||
|
||||
**设计说明:姓名 / 班级不冗余,走 JOIN 获取**
|
||||
|
||||
与 "在就业表冗余学生姓名" 的方案不同,本模块选择通过 `stuid`、`class_id` 外键关联学生表与班级表,查询时动态 JOIN 获取姓名与班级名。代价是查询多一次 JOIN;好处是学生姓名 / 班级变更后无需同步,数据天然一致。
|
||||
|
||||
## 四、API 功能清单(CRUD)
|
||||
|
||||
接口定义于 `CURD_api.py` 的 `CURD` 路由,由根 `../main.py` 以 `Shtudent_Jiuye` 挂载。
|
||||
|
||||
### 📌 新增(Create)
|
||||
|
||||
|
||||
|
||||
| 方法 | 路径 | 功能 | 请求体 |
|
||||
| ---- | -------------------- | -------- | ----------- |
|
||||
| POST | /emp/company/address | 新增地址区域 | AddRequest |
|
||||
| POST | /emp/company | 新增公司 | CompRequest |
|
||||
| POST | /emp | 新增学生就业信息 | PostRequest |
|
||||
|
||||
> 注意:由于外键依赖,建议按 Address → Company → Employments 的顺序添加。
|
||||
|
||||
### 📌 查询(Read)
|
||||
|
||||
|
||||
|
||||
| 方法 | 路径 | 功能 | 查询参数 |
|
||||
| --- | ---- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| GET | /emp | 多条件查询就业信息 | sname、class\_id、employment\_company、min\_salary、max\_salary、somewhere、employment\_open\_start、employment\_open\_end、offer\_recived\_start、offer\_recived\_end(全部可选,均不传则查询全部) |
|
||||
|
||||
> 就业薪资 Top5、就业时长、平均就业时长等统计接口已迁移至统计分析模块(StatCalc):
|
||||
>
|
||||
> `/statcalc/top`
|
||||
>
|
||||
> 、
|
||||
>
|
||||
> `/statcalc/job`
|
||||
>
|
||||
> 、
|
||||
>
|
||||
> `/statcalc/avgjob`
|
||||
>
|
||||
> 。
|
||||
|
||||
### 📌 更新(Update)
|
||||
|
||||
|
||||
|
||||
| 方法 | 路径 | 功能 | 参数 |
|
||||
| --- | --------- | ------ | ----------------------------------- |
|
||||
| PUT | /emp?id=X | 修改就业信息 | Query:id;Body:PostRequest(支持部分字段更新) |
|
||||
|
||||
### 📌 删除(Delete)
|
||||
|
||||
|
||||
|
||||
| 方法 | 路径 | 功能 | 参数 |
|
||||
| ------ | --------- | ------- | ------- |
|
||||
| DELETE | /emp/{id} | 软删除就业信息 | Path:id |
|
||||
|
||||
**删除说明**:本模块采用软删除(`is_deleted = 1`),数据不会从物理表中移除,便于审计与恢复。
|
||||
|
||||
## 五、核心流程示例
|
||||
|
||||
### 查询流程(GET /emp)
|
||||
|
||||
|
||||
|
||||
```
|
||||
前端 → CURD\_api.get\_stuinfo()
|
||||
|
||||
  ↓ 收集 10 个可选查询条件
|
||||
|
||||
  ↓ 调用 get\_emp\_dao()
|
||||
|
||||
  ↓ DAO 五表 OUTER JOIN:Employments + Students + ClassInfo + Company + Address
|
||||
|
||||
  ↓ 动态拼接 filter(姓名/班级/公司/薪资范围/地址/时间范围)
|
||||
|
||||
  ↓ 统一过滤 is\_deleted = 0
|
||||
|
||||
  ↓ 返回 list\[dict](含学生姓名、班级名称、公司、区域、薪资、时间)
|
||||
|
||||
  ↓ 结果为空 → 404「名单不存在」
|
||||
```
|
||||
|
||||
### 添加流程(POST /emp)
|
||||
|
||||
|
||||
|
||||
```
|
||||
前端 → CURD\_api.add\_stuinfo()
|
||||
|
||||
  ↓ p.model\_dump() 转 dict
|
||||
|
||||
  ↓ add\_employment\_dao()
|
||||
|
||||
  ↓ o.pop('id' / 'create\_time' / 'update\_time' / 'is\_deleted') 去掉自增主键与自动字段
|
||||
|
||||
  ↓ Employments(\*\*o) 实例化
|
||||
|
||||
  ↓ db.add → db.commit → db.refresh
|
||||
|
||||
  ↓ 失败 → db.rollback → 返回 500
|
||||
```
|
||||
|
||||
### 更新流程(PUT /emp?id=X)
|
||||
|
||||
|
||||
|
||||
```
|
||||
前端 → CURD\_api.updata\_stuinfo()
|
||||
|
||||
  ↓ 请求体排除未传字段(exclude\_unset)
|
||||
|
||||
  ↓ 为空 → 400「没有需要更新的字段」
|
||||
|
||||
  ↓ update\_emp\_dao() 按 id 执行 update
|
||||
|
||||
  ↓ 影响 0 行 → 404「未找到对应记录」
|
||||
```
|
||||
|
||||
### 删除流程(DELETE /emp/{id})
|
||||
|
||||
|
||||
|
||||
```
|
||||
前端 → CURD\_api.delete\_stuinfo()
|
||||
|
||||
  ↓ delete\_emp\_dao() 按 id 执行 UPDATE is\_deleted = 1(软删除)
|
||||
|
||||
  ↓ 影响 0 行 → 500「没有删除」
|
||||
```
|
||||
|
||||
## 六、项目亮点
|
||||
|
||||
|
||||
|
||||
1. **清晰的分层架构**
|
||||
|
||||
路由(CURD\_api.py)、数据访问(dao.py)、模型(model.py)、校验(request.py)各司其职,便于单元测试与维护。
|
||||
|
||||
2. **全链路软删除**
|
||||
|
||||
Employments、Company、Address 三张表均有 `is_deleted` 字段,删除统一走 `UPDATE is_deleted = 1`,数据可追溯、可恢复;查询时统一过滤 `is_deleted == 0`。
|
||||
|
||||
3. **动态多条件组合查询**
|
||||
|
||||
`get_emp_dao` 使用链式 filter 动态拼接查询条件,任一条件为空自动跳过,一个接口覆盖 10 种查询维度(姓名 / 班级 / 公司 / 薪资区间 / 地址 / 开放时间区间 / Offer 时间区间)。
|
||||
|
||||
4. **统一时间戳审计**
|
||||
|
||||
所有表均带 `create_time` / `update_time`,`onupdate=datetime.now` 让修改自动记录时间,为审计、报表提供数据基础。
|
||||
|
||||
5. **请求体与响应体分离**
|
||||
|
||||
request.py 为每类资源定义独立请求体(PostRequest / CompRequest / AddRequest)与响应体(EmpResponse / CompResponse / AddResponse),前端契约清晰,Swagger 自动生成接口文档。
|
||||
|
||||
6. **多表 JOIN 关联查询**
|
||||
|
||||
就业记录与学生、班级、公司、地址四张表通过外键关联,一次查询即可返回完整就业信息,无需前端二次请求。
|
||||
|
||||
7. **外键约束保证数据完整性**
|
||||
|
||||
stuid /class\_id/company\_id /address\_id 全部建立外键,杜绝 "就业记录指向不存在学生 / 公司" 的脏数据。
|
||||
|
||||
## 七、待添加的新功能 与 已知问题
|
||||
|
||||
### 🎯 短期(可快速落地)
|
||||
|
||||
|
||||
|
||||
| 功能 | 说明 | 涉及改动 |
|
||||
| ------- | ------------------------------------------------ | ---------------------------------------------- |
|
||||
| 分页查询 | /emp 支持 page /page\_size,返回 total 与 items | dao.py 加 .offset ().limit (),request.py 加分页请求体 |
|
||||
| 薪资区间校验 | 确保 min\_salary <= max\_salary | request.py 加 @model\_validator |
|
||||
| 数据去重 | 同一学生同一公司只保留一条有效就业记录 | add\_employment\_dao 前置查询 + 唯一约束 |
|
||||
| 级联删除 | 删公司 / 地址时同步软删其下就业记录 | dao.py 加级联更新逻辑 |
|
||||
| 外键存在性校验 | 新增时校验 stuid /company\_id/address\_id 真实存在,给出明确错误 | dao.py 前置查询 |
|
||||
|
||||
### 🚀 中期(需要设计)
|
||||
|
||||
|
||||
|
||||
| 功能 | 说明 |
|
||||
| ---------- | --------------------------------- |
|
||||
| 批量导入 | 支持 Excel/CSV 批量导入就业信息 |
|
||||
| 数据导出 | 查询结果一键导出为 Excel |
|
||||
| JWT 鉴权 | 管理员 / 普通用户角色分离,保护写接口 |
|
||||
| 操作日志 | 记录每一次增删改操作的操作人与时间 |
|
||||
| Alembic 迁移 | 用 Alembic 管理表结构变更,告别手动 DROP TABLE |
|
||||
|
||||
### 🌟 长期(数据价值)
|
||||
|
||||
|
||||
|
||||
| 功能 | 说明 |
|
||||
| ------ | ----------------------- |
|
||||
| 就业数据看板 | 按班级 / 公司 / 薪资维度统计,前端可视化 |
|
||||
| 薪资分布分析 | 直方图、分位数、同比环比 |
|
||||
| 就业率追踪 | 结合学生表计算班级 / 专业就业率 |
|
||||
| 缓存层 | Redis 缓存 Top5、统计类接口 |
|
||||
| 异步任务 | Celery 处理导入 / 导出等耗时任务 |
|
||||
|
||||
### ⚠️ 当前已知问题(开发中待修复)
|
||||
|
||||
|
||||
|
||||
| 问题 | 位置 | 影响 | 建议修复 |
|
||||
| ----------------- | -------------------------------------------------------- | -------------------- | ------------------------- |
|
||||
| 查询结果按 5 变量解包 3 元组 | dao.py get\_emp\_dao | 查到数据即 ValueError 崩溃 | select 补齐 5 列,或解包变量与列数一致 |
|
||||
| 空日期直接 strftime | dao.py 返回段 | 时间为空时 AttributeError | 判空后再格式化 |
|
||||
| 删除 0 行返回 500 | CURD\_api.py delete\_stuinfo | 状态码语义错误 | 改 404 |
|
||||
| 查询空结果返回 404 | CURD\_api.py get\_stuinfo | 正常空结果被当异常 | 改 200 + 空列表 + totals |
|
||||
| 更新未过滤已删除记录 | dao.py update\_emp\_dao | 可修改已软删数据 | filter 加 is\_deleted == 0 |
|
||||
| SQL 全量打印 | database.py echo=True | 开发噪音 / 性能损耗 | 生产环境关闭 |
|
||||
| 命名不统一 | Shtudent\_Jiuye / updata\_stuinfo / offer\_recived\_time | 可读性差 | 统一规范命名 |
|
||||
|
||||
## 八、运行方式
|
||||
|
||||
|
||||
|
||||
```
|
||||
\# 1. 安装依赖
|
||||
|
||||
pip install fastapi uvicorn sqlalchemy pymysql pydantic python-dotenv
|
||||
|
||||
\# 2. 配置数据库(项目根目录 .env 中 DB\_USER / DB\_PASSWORD / DB\_HOST / DB\_PORT / DB\_NAME)
|
||||
|
||||
\# 3. 启动服务(根入口,含全模块)
|
||||
|
||||
python main.py
|
||||
|
||||
\# 或
|
||||
|
||||
uvicorn main:app --host 127.0.0.1 --port 59000 --reload
|
||||
|
||||
\# 4. 单独调试就业模块
|
||||
|
||||
uvicorn stu\_jiuye.main:app --host 127.0.0.1 --port 12395 --reload
|
||||
|
||||
\# 5. 访问 Swagger 文档
|
||||
|
||||
http://127.0.0.1:59000/docs
|
||||
```
|
||||
|
||||
## 九、注意事项
|
||||
|
||||
|
||||
|
||||
1. **表结构变更需删表重建**:`Base.metadata.create_all(engine)` 只建不改,模型加字段后需 DROP TABLE 或引入 Alembic。
|
||||
|
||||
2. **建表时机**:当前建表逻辑在根 `../main.py` 的 `__main__` 块中执行;直接用 `uvicorn main:app` 启动时不会建表,全新环境需先执行一次 `python main.py`(或后续将建表移入 FastAPI lifespan)。
|
||||
|
||||
3. **添加顺序有外键依赖**:先建学生 / 班级 / 公司 / 地址,再添加就业记录;Employments 的 stuid /class\_id/company\_id /address\_id 均受外键约束。
|
||||
|
||||
4. **软删除后查询自动过滤**:`get_emp_dao` 已统一过滤 `is_deleted == 0`,被删除的记录不会再出现在列表里。
|
||||
|
||||
5. **删除接口用 Path 参数**:`DELETE /emp/{id}`,不要写成 `/emp?id=X`。
|
||||
|
||||
6. **echo=True 仅限开发**:生产环境请关掉,避免刷屏与性能损耗。
|
||||
@@ -0,0 +1,270 @@
|
||||
# 学生就业信息管理系统 —— 就业模块项目文档
|
||||
|
||||
## 一、模块概述
|
||||
|
||||
本模块是「学生就业信息管理系统」的核心业务模块,负责管理学生就业状态的全生命周期。围绕就业信息(Employments)、公司信息(Company)、地址信息(Address)三张核心表,并与学生表(students)、班级表(classes)建立外键关联,提供增删改查(CRUD)与统计分析接口。
|
||||
|
||||
**技术栈:**
|
||||
|
||||
| 项 | 选型 |
|
||||
|---|---|
|
||||
| Web 框架 | FastAPI |
|
||||
| ORM | SQLAlchemy 2.x |
|
||||
| 数据库 | MySQL 8.x(驱动 PyMySQL) |
|
||||
| 数据校验 | Pydantic v2 |
|
||||
| 服务启动 | Uvicorn |
|
||||
|
||||
## 二、目录结构
|
||||
|
||||
```
|
||||
student_manage_system/
|
||||
├── main.py # 程序入口:注册全模块路由、建表(__main__ 中执行)
|
||||
├── databases.py # 根数据库配置:engine_all / Base_all / Session / get_db
|
||||
└── stu_jiuye/ # 就业模块业务包
|
||||
├── main.py # 路由组装(Shtudent_Jiuye);独立自测 app
|
||||
├── database.py # 模块级数据库配置(engine / Base / Session / get_db)
|
||||
├── model.py # ORM 模型:Employments / Company / Address
|
||||
├── request.py # Pydantic 请求体 / 响应体模型
|
||||
├── dao.py # 数据访问层(纯数据库操作)
|
||||
└── CURD_api.py # 路由层(API 接口定义)
|
||||
```
|
||||
|
||||
**分层职责:**
|
||||
|
||||
| 层 | 文件 | 职责 |
|
||||
|---|---|---|
|
||||
| 路由层 | CURD_api.py | 接收 HTTP 请求,参数校验,调用 DAO,返回响应 |
|
||||
| 数据访问层 | dao.py | 封装 SQLAlchemy 查询与事务,向上提供函数 |
|
||||
| 模型层 | model.py | 定义表结构与外键关系 |
|
||||
| 校验层 | request.py | 定义请求体 / 响应体的字段与类型 |
|
||||
| 配置层 | database.py | 数据库 URL、引擎、Session 工厂 |
|
||||
|
||||
## 三、数据表设计
|
||||
|
||||
### 1. Employments(就业信息表)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | Integer | PK, 自增 | 主键 |
|
||||
| stuid | Integer | FK → students.id | 学生 ID |
|
||||
| class_id | Integer | FK → classes.id | 班级 ID |
|
||||
| company_id | Integer | FK → Company.id, 可空 | 就业公司 |
|
||||
| address_id | Integer | FK → Address.id, NOT NULL | 就业地址 |
|
||||
| employment_salary | Integer | 可空 | 就业薪资 |
|
||||
| employment_open_time | DATE | 可空 | 就业开放时间 |
|
||||
| offer_recived_time | DATE | 可空 | Offer 下发时间 |
|
||||
| create_time | DateTime | default=now | 创建时间 |
|
||||
| update_time | DateTime | default=now, onupdate=now | 更新时间 |
|
||||
| is_deleted | Integer | default=0 | 软删除标记(0 正常 / 1 已删) |
|
||||
|
||||
### 2. Company(公司表)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | Integer | PK, 自增 | 主键 |
|
||||
| employment_company | String(50) | NOT NULL | 公司名称 |
|
||||
| create_time | DateTime | default=now | 创建时间 |
|
||||
| update_time | DateTime | default=now, onupdate=now | 更新时间 |
|
||||
| is_deleted | Integer | default=0 | 软删除标记 |
|
||||
|
||||
### 3. Address(地址表)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | Integer | PK, 自增 | 主键 |
|
||||
| somewhere | String(50) | NOT NULL | 地址区域,如"深圳市南山区" |
|
||||
| create_time | DateTime | default=now | 创建时间 |
|
||||
| update_time | DateTime | default=now, onupdate=now | 更新时间 |
|
||||
| is_deleted | Integer | default=0 | 软删除标记 |
|
||||
|
||||
### 表关系图
|
||||
|
||||
```
|
||||
students (1) ───< (N) Employments (N) >─── (1) Company
|
||||
classes (1) ───< (N) | (N) >─── (1) Address
|
||||
```
|
||||
|
||||
- 一个学生可有多个就业记录,一条就业记录对应一个学生(通过 stuid 关联学生表)
|
||||
- 一条就业记录对应一个公司(company_id)与一个地址(address_id)
|
||||
- 通过多表 JOIN 可从就业记录直接获取学生姓名、班级名称、公司名称与地址信息
|
||||
|
||||
**设计说明:姓名/班级不冗余,走 JOIN 获取**
|
||||
|
||||
与"在就业表冗余学生姓名"的方案不同,本模块选择通过 `stuid`、`class_id` 外键关联学生表与班级表,查询时动态 JOIN 获取姓名与班级名。代价是查询多一次 JOIN;好处是学生姓名/班级变更后无需同步,数据天然一致。
|
||||
|
||||
## 四、API 功能清单(CRUD)
|
||||
|
||||
接口定义于 `stu_jiuye/CURD_api.py` 的 `CURD` 路由,由根 `main.py` 以 `Shtudent_Jiuye` 挂载。
|
||||
|
||||
### 📌 新增(Create)
|
||||
|
||||
| 方法 | 路径 | 功能 | 请求体 |
|
||||
|---|---|---|---|
|
||||
| POST | /emp/company/address | 新增地址区域 | AddRequest |
|
||||
| POST | /emp/company | 新增公司 | CompRequest |
|
||||
| POST | /emp | 新增学生就业信息 | PostRequest |
|
||||
|
||||
> 注意:由于外键依赖,建议按 Address → Company → Employments 的顺序添加。
|
||||
|
||||
### 📌 查询(Read)
|
||||
|
||||
| 方法 | 路径 | 功能 | 查询参数 |
|
||||
|---|---|---|---|
|
||||
| GET | /emp | 多条件查询就业信息 | sname、class_id、employment_company、min_salary、max_salary、somewhere、employment_open_start、employment_open_end、offer_recived_start、offer_recived_end(全部可选,均不传则查询全部) |
|
||||
|
||||
> 就业薪资 Top5、就业时长、平均就业时长等统计接口已迁移至统计分析模块(StatCalc):`/statcalc/top`、`/statcalc/job`、`/statcalc/avgjob`。
|
||||
|
||||
### 📌 更新(Update)
|
||||
|
||||
| 方法 | 路径 | 功能 | 参数 |
|
||||
|---|---|---|---|
|
||||
| PUT | /emp?id=X | 修改就业信息 | Query:id;Body:PostRequest(支持部分字段更新) |
|
||||
|
||||
### 📌 删除(Delete)
|
||||
|
||||
| 方法 | 路径 | 功能 | 参数 |
|
||||
|---|---|---|---|
|
||||
| DELETE | /emp/{id} | 软删除就业信息 | Path:id |
|
||||
|
||||
**删除说明**:本模块采用软删除(`is_deleted = 1`),数据不会从物理表中移除,便于审计与恢复。
|
||||
|
||||
## 五、核心流程示例
|
||||
|
||||
### 查询流程(GET /emp)
|
||||
|
||||
```
|
||||
前端 → CURD_api.get_stuinfo()
|
||||
↓ 收集 10 个可选查询条件
|
||||
↓ 调用 get_emp_dao()
|
||||
↓ DAO 五表 OUTER JOIN:Employments + Students + ClassInfo + Company + Address
|
||||
↓ 动态拼接 filter(姓名/班级/公司/薪资范围/地址/时间范围)
|
||||
↓ 统一过滤 is_deleted = 0
|
||||
↓ 返回 list[dict](含学生姓名、班级名称、公司、区域、薪资、时间)
|
||||
↓ 结果为空 → 404「名单不存在」
|
||||
```
|
||||
|
||||
### 添加流程(POST /emp)
|
||||
|
||||
```
|
||||
前端 → CURD_api.add_stuinfo()
|
||||
↓ p.model_dump() 转 dict
|
||||
↓ add_employment_dao()
|
||||
↓ o.pop('id' / 'create_time' / 'update_time' / 'is_deleted') 去掉自增主键与自动字段
|
||||
↓ Employments(**o) 实例化
|
||||
↓ db.add → db.commit → db.refresh
|
||||
↓ 失败 → db.rollback → 返回 500
|
||||
```
|
||||
|
||||
### 更新流程(PUT /emp?id=X)
|
||||
|
||||
```
|
||||
前端 → CURD_api.updata_stuinfo()
|
||||
↓ 请求体排除未传字段(exclude_unset)
|
||||
↓ 为空 → 400「没有需要更新的字段」
|
||||
↓ update_emp_dao() 按 id 执行 update
|
||||
↓ 影响 0 行 → 404「未找到对应记录」
|
||||
```
|
||||
|
||||
### 删除流程(DELETE /emp/{id})
|
||||
|
||||
```
|
||||
前端 → CURD_api.delete_stuinfo()
|
||||
↓ delete_emp_dao() 按 id 执行 UPDATE is_deleted = 1(软删除)
|
||||
↓ 影响 0 行 → 500「没有删除」
|
||||
```
|
||||
|
||||
## 六、项目亮点
|
||||
|
||||
1. **清晰的分层架构**
|
||||
路由(CURD_api.py)、数据访问(dao.py)、模型(model.py)、校验(request.py)各司其职,便于单元测试与维护。
|
||||
|
||||
2. **全链路软删除**
|
||||
Employments、Company、Address 三张表均有 `is_deleted` 字段,删除统一走 `UPDATE is_deleted = 1`,数据可追溯、可恢复;查询时统一过滤 `is_deleted == 0`。
|
||||
|
||||
3. **动态多条件组合查询**
|
||||
`get_emp_dao` 使用链式 filter 动态拼接查询条件,任一条件为空自动跳过,一个接口覆盖 10 种查询维度(姓名/班级/公司/薪资区间/地址/开放时间区间/Offer 时间区间)。
|
||||
|
||||
4. **统一时间戳审计**
|
||||
所有表均带 `create_time` / `update_time`,`onupdate=datetime.now` 让修改自动记录时间,为审计、报表提供数据基础。
|
||||
|
||||
5. **请求体与响应体分离**
|
||||
request.py 为每类资源定义独立请求体(PostRequest / CompRequest / AddRequest)与响应体(EmpResponse / CompResponse / AddResponse),前端契约清晰,Swagger 自动生成接口文档。
|
||||
|
||||
6. **多表 JOIN 关联查询**
|
||||
就业记录与学生、班级、公司、地址四张表通过外键关联,一次查询即可返回完整就业信息,无需前端二次请求。
|
||||
|
||||
7. **外键约束保证数据完整性**
|
||||
stuid / class_id / company_id / address_id 全部建立外键,杜绝"就业记录指向不存在学生/公司"的脏数据。
|
||||
|
||||
## 七、待添加的新功能 与 已知问题
|
||||
|
||||
### 🎯 短期(可快速落地)
|
||||
|
||||
| 功能 | 说明 | 涉及改动 |
|
||||
|---|---|---|
|
||||
| 分页查询 | /emp 支持 page / page_size,返回 total 与 items | dao.py 加 .offset().limit(),request.py 加分页请求体 |
|
||||
| 薪资区间校验 | 确保 min_salary <= max_salary | request.py 加 @model_validator |
|
||||
| 数据去重 | 同一学生同一公司只保留一条有效就业记录 | add_employment_dao 前置查询 + 唯一约束 |
|
||||
| 级联删除 | 删公司/地址时同步软删其下就业记录 | dao.py 加级联更新逻辑 |
|
||||
| 外键存在性校验 | 新增时校验 stuid / company_id / address_id 真实存在,给出明确错误 | dao.py 前置查询 |
|
||||
|
||||
### 🚀 中期(需要设计)
|
||||
|
||||
| 功能 | 说明 |
|
||||
|---|---|
|
||||
| 批量导入 | 支持 Excel/CSV 批量导入就业信息 |
|
||||
| 数据导出 | 查询结果一键导出为 Excel |
|
||||
| JWT 鉴权 | 管理员 / 普通用户角色分离,保护写接口 |
|
||||
| 操作日志 | 记录每一次增删改操作的操作人与时间 |
|
||||
| Alembic 迁移 | 用 Alembic 管理表结构变更,告别手动 DROP TABLE |
|
||||
|
||||
### 🌟 长期(数据价值)
|
||||
|
||||
| 功能 | 说明 |
|
||||
|---|---|
|
||||
| 就业数据看板 | 按班级/公司/薪资维度统计,前端可视化 |
|
||||
| 薪资分布分析 | 直方图、分位数、同比环比 |
|
||||
| 就业率追踪 | 结合学生表计算班级/专业就业率 |
|
||||
| 缓存层 | Redis 缓存 Top5、统计类接口 |
|
||||
| 异步任务 | Celery 处理导入/导出等耗时任务 |
|
||||
|
||||
###
|
||||
|
||||
| | | | |
|
||||
|---|---|---|---|
|
||||
| | | | |
|
||||
| | | | |
|
||||
| | | | |
|
||||
| | | | |
|
||||
| | | | |
|
||||
| | | | |
|
||||
| | | | |
|
||||
|
||||
## 八、运行方式
|
||||
|
||||
```bash
|
||||
# 1. 安装依赖
|
||||
pip install fastapi uvicorn sqlalchemy pymysql pydantic python-dotenv
|
||||
|
||||
# 2. 配置数据库(项目根目录 .env 中 DB_USER / DB_PASSWORD / DB_HOST / DB_PORT / DB_NAME)
|
||||
|
||||
# 3. 启动服务(根入口,含全模块)
|
||||
python main.py
|
||||
# 或
|
||||
uvicorn main:app --host 127.0.0.1 --port 59000 --reload
|
||||
|
||||
# 4. 单独调试就业模块
|
||||
uvicorn stu_jiuye.main:app --host 127.0.0.1 --port 12395 --reload
|
||||
|
||||
# 5. 访问 Swagger 文档
|
||||
http://127.0.0.1:59000/docs
|
||||
```
|
||||
|
||||
## 九、注意事项
|
||||
|
||||
1. **表结构变更需删表重建**:`Base.metadata.create_all(engine)` 只建不改,模型加字段后需 DROP TABLE 或引入 Alembic。
|
||||
2. **建表时机**:当前建表逻辑在根 `main.py` 的 `__main__` 块中执行;直接用 `uvicorn main:app` 启动时不会建表,全新环境需先执行一次 `python main.py`(或后续将建表移入 FastAPI lifespan)。
|
||||
3. **添加顺序有外键依赖**:先建学生/班级/公司/地址,再添加就业记录;Employments 的 stuid / class_id / company_id / address_id 均受外键约束。
|
||||
4. **软删除后查询自动过滤**:`get_emp_dao` 已统一过滤 `is_deleted == 0`,被删除的记录不会再出现在列表里。
|
||||
5. **删除接口用 Path 参数**:`DELETE /emp/{id}`,不要写成 `/emp?id=X`。
|
||||
6. **echo=True 仅限开发**:生产环境请关掉,避免刷屏与性能损耗。
|
||||
@@ -27,15 +27,15 @@ def get_students( stu:StudentsQuery = Depends()
|
||||
@s_api.post("/students"
|
||||
,summary='新增学生资料'
|
||||
,description=f'学生ID自动分配,学生姓名、学生手机号和班级是必填项,不能为空。\n '
|
||||
f'时间格式:YYYY-MM-DD'
|
||||
f'注意事项:\n'
|
||||
f'时间格式:YYYY-MM-DD\n'
|
||||
f'班级ID和顾问ID必须已经存在才可以绑定'
|
||||
,response_model=StudentsResponse )
|
||||
def add_student( stu:StudentsResponse = Depends()
|
||||
, db=Depends(get_db)
|
||||
):
|
||||
s_dict = stu.model_dump(exclude_unset=True)
|
||||
result = add_student_dao( s=s_dict , db=db )
|
||||
if not result:
|
||||
raise HTTPException(status_code=500, detail='手机号已有学生使用或必填项为空。')
|
||||
return result
|
||||
|
||||
@s_api.put("/students"
|
||||
|
||||
@@ -62,9 +62,11 @@ def add_student_dao( s , db):
|
||||
phone = s.get("phone")
|
||||
s1_deleted = db.query( Students ).filter( Students.phone == phone ).first()
|
||||
if s1_deleted and s1_deleted.is_deleted == False:
|
||||
return False
|
||||
raise ValueError("该手机号已存在有效学生记录,请勿重复添加")
|
||||
if not phone:
|
||||
return False
|
||||
raise ValueError("手机号不能为空")
|
||||
if not s.get("class_id") or not s.get("advisor_id"):
|
||||
raise ValueError("班级ID和顾问ID必须已经存在才可以绑定")
|
||||
if s1_deleted and s1_deleted.is_deleted == True:
|
||||
s1_deleted.name = s.get("name")
|
||||
s1_deleted.age = s.get("age")
|
||||
@@ -90,10 +92,14 @@ def add_student_dao( s , db):
|
||||
return s1_deleted
|
||||
s1 = Students( **s )
|
||||
db.add(s1)
|
||||
except ValueError as e:
|
||||
# 业务校验异常:回滚并返回错误信息
|
||||
db.rollback()
|
||||
return {"code": 400, "msg": str(e)}
|
||||
except Exception:
|
||||
# 这里可以加日志
|
||||
db.rollback()
|
||||
return False
|
||||
return {"code": 500, "msg": f"系统异常: {str(e)}"}
|
||||
else:
|
||||
db.commit()
|
||||
db.refresh(s1)
|
||||
|
||||
+4
-3
@@ -8,6 +8,7 @@ from contextlib import asynccontextmanager
|
||||
Students_API = APIRouter()
|
||||
Students_API.include_router(s_api)
|
||||
|
||||
|
||||
# 自测接口
|
||||
@asynccontextmanager
|
||||
async def S_Fastapi(app: FastAPI):
|
||||
@@ -15,11 +16,11 @@ async def S_Fastapi(app: FastAPI):
|
||||
yield
|
||||
engine1.dispose()
|
||||
|
||||
app = FastAPI(title="学生管理系统",lifespan=S_Fastapi) # 这里必须把上面写的加上
|
||||
app.include_router(s_api,tags=["students"])
|
||||
|
||||
app = FastAPI(title="学生管理系统", lifespan=S_Fastapi) # 这里必须把上面写的加上
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.include_router(s_api, tags=["students"])
|
||||
|
||||
def main():
|
||||
print("Hello from student-manage-system!")
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from pydantic import BaseModel , Field , field_validator
|
||||
from datetime import datetime
|
||||
import enum
|
||||
|
||||
class SexEnum(enum.Enum):
|
||||
class SexEnum(str , enum.Enum):
|
||||
m = '男'
|
||||
f = '女'
|
||||
|
||||
@@ -27,7 +27,7 @@ class StudentsQuery(BaseModel):
|
||||
# 额外字段
|
||||
phone:str | None = None
|
||||
|
||||
class EducationEnum(enum.Enum):
|
||||
class EducationEnum(str , enum.Enum):
|
||||
z = '专科'
|
||||
ben = '本科'
|
||||
s = '硕士'
|
||||
|
||||
Reference in New Issue
Block a user