Compare commits

...
23 Commits
Author SHA1 Message Date
geyuan b67e3cd547 修更新的bud 2026-09-23 00:50:59 +08:00
chen 079a7cf351 模块解释 2026-09-22 23:02:42 +08:00
Coffee 2dca7c7f76 更新debug 2026-09-22 23:02:42 +08:00
Orangeeee 566747b320 更新Student的README_students.md模块解释文件。 2026-09-22 23:02:42 +08:00
Orangeeee 699867b0af 优化了students_dao里add_student_dao的报错逻辑。 2026-09-22 23:02:42 +08:00
Orangeeee fa571a13da 优化了students_dao里add_student_dao的报错逻辑。 2026-09-22 23:02:42 +08:00
Coffee 5fddb78c27 更新db.refresh 2026-09-22 23:02:42 +08:00
Coffee b5883e450d 更新debug 2026-09-22 23:02:39 +08:00
Coffee dc243fb33f 更新外键 2026-09-22 23:01:59 +08:00
Anhoa 6e189d0c65 更改新增dao bug 2026-09-22 23:01:59 +08:00
chen f2e525b5a5 统计 2026-09-22 23:01:59 +08:00
Orangeeee a1d7adba94 全部更新确认,无基础错误,各自测试各自端口bug,暂时没有自动导入数据。 2026-09-22 23:01:59 +08:00
chen 7d5f352c69 统计 2026-09-22 23:01:56 +08:00
Orangeeee ffcb40db41 统计分析模块更新,待测试,统计分析模块更新,就业3个模块更新。 2026-09-22 23:01:21 +08:00
chen 75f23e39be dao改动 2026-09-22 23:01:21 +08:00
longtrying c1813ae700 删除 .env
以免频繁重新覆盖修改。
2026-09-22 23:01:21 +08:00
Anhoa 65d67e7cf6 增加了num的唯一键,修改了dao和api 2026-09-22 23:01:18 +08:00
Orangeeee c0880f9a44 统计分析模块更新,待测试,优化所有模块main.py文件,修复bug。 2026-09-22 22:59:50 +08:00
geyuan 9c9e51d68f 改提交不上的bud 2026-09-22 22:55:17 +08:00
geyuan 334bb237b2 改提交不上的bud 2026-09-22 22:32:44 +08:00
geyuan 737d48babc 加电话号码的限制 2026-09-22 21:20:24 +08:00
geyuan 17c739760f 文字说明 2026-09-22 21:01:35 +08:00
geyuan 748b9f751c 新增科目增加接口 2026-09-22 20:43:33 +08:00
36 changed files with 629 additions and 316 deletions
-7
View File
@@ -1,7 +0,0 @@
# .env(放项目根目录,每个人自己建,不提交 Git)
# 用来修改数据库账户密码
DB_USER=root
DB_PASSWORD=420821
DB_HOST=localhost
DB_PORT=3306
DB_NAME=student_manage_system
+18 -6
View File
@@ -10,23 +10,35 @@
2.教师信息的新增\
3.教师信息的更新\
4.教师信息的逻辑删除\
4.1输入id查\
4.2输入任意信息查\
5.学科信息的新增\
**dao数据筛选文件**\
1.查询 get_tea_dao:\
2.新增 add_tea_dao:\
3.更新 update_tea_dao:\
4.逻辑删除 delete_tea_dao:\
4.根据教师id逻辑删除 delete_tea_dao:\
5.任意信息逻辑删除\
6.科目信息的新增\
**tea_model数据库建表文件**\
1.教师表\
2.科目表\
字段名:id、name、sex、class_id、sub(学科)\
home_place specialty(专业)、start_time(入职时间)\
work_experience、collage、creat_date、update_date\
is_delete、delete_time\
is_delete、delete_time、phone\
2.学科表\
字段名:id,name(科目名字)
字段名:id,name(科目名字)\
**schema 请求体、响应体**\
请求体:设置了两个,一个带自增组件用于查询\
一个不带自增组件,新增用;\
响应体:用于返回给前端
教师表\
请求体:\
1.查询、任意逻辑删除调用
2.新增、更改时调用
响应体:用于返回给前端\
科目表:\
1.请求体:新增科目时调用
+26 -69
View File
@@ -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)
}
+31 -3
View File
@@ -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
View File
@@ -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()
+1 -1
View File
@@ -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 = "班级中男女人数"
+22 -9
View File
@@ -3,14 +3,15 @@ from Teachers.schema import *
from Teachers.database import *
from Teachers.model.tea_model import *
from Teachers.dao.tea_dao import *
from Teachers.schema.tea_request import TeaRequest,NewTeaRequest,NewTeaRequest1
from Teachers.schema.tea_request import TeaRequest,NewTeaRequest,NewTeaRequest1,SubRequest
from Teachers.schema.tea_response import TeaResponse
tea_api = APIRouter()
@tea_api.post('/tea',response_model=TeaResponse,summary='新增老师',description='输入新增的老师信息,除去姓名外可以为空')
def add_tea(tea: NewTeaRequest=Depends(),db=Depends(get_db)):
r = add_tea_dao(tea.model_dump(),db)
r = add_tea_dao(tea.model_dump(exclude_unset=True),db)
if not r:
raise HTTPException(status_code=500,detail='添加失败!')
return TeaResponse(totals=1,data=tea.model_dump())
@@ -27,13 +28,17 @@ def delete_tea(id:int|None,db=Depends(get_db)):
raise HTTPException(status_code=500,detail='删除失败!')
return {'detail':'删除成功!'}
@tea_api.put('/tea/{id}',summary='更新老师信息',description='输入更改的信息,可以为空')
def update_tea(id:int,tea:NewTeaRequest,db=Depends(get_db)):
t = tea.model_dump(exclude_unset= True)
r = update_tea_dao(id=id,t=t,db=db)
if not r:
raise HTTPException(status_code=404,detail='老师不存在')
return {'detail':'更新成功'}
@tea_api.put('/tea/{id}', summary='更新老师信息', description='输入更改的信息,可以为空')
def update_tea(id: int, tea: NewTeaRequest = Depends(), db=Depends(get_db)):
t = tea.model_dump(exclude_unset=True)
r = update_tea_dao(id=id, t=t, db=db)
if r == 'NOT_FOUND':
raise HTTPException(status_code=404, detail='老师不存在')
if r == 'NO_CHANGE':
raise HTTPException(status_code=400, detail='没有要更新的内容')
if isinstance(r, str) and r.startswith('ERROR'):
raise HTTPException(status_code=400, detail=r)
return {'detail': '更新成功'}
#根据任意信息删除
@tea_api.delete('/tea',summary='根据任意信息逻辑删除',description='输入任意信息删除')
@@ -43,4 +48,12 @@ def delete_other_tea_dao1(t:TeaRequest=Depends(),db=Depends(get_db)):
raise HTTPException(status_code=404,detail='没有找到符合条件的老师')
return{'code':200,'data':row1}
#新增科目
@tea_api.post('/subject',summary='新增科目',description='输入新增的科目')
def add_sub(t:SubRequest=Depends(),db=Depends(get_db)): #开启会话窗口
r = add_subject_dao(t.model_dump(),db=db)
if r=='已存在':
raise HTTPException(status_code=500,detail='科目已存在')
return TeaResponse(totals=len(r), data=r)
+40 -13
View File
@@ -1,4 +1,6 @@
from Teachers.model.tea_model import Tea
from sys import excepthook
from Teachers.model.tea_model import Tea,Sub
from datetime import datetime
#增加
@@ -27,15 +29,22 @@ def add_tea_dao(t,db):
#更新
def update_tea_dao(id,t, db):
def update_tea_dao(id, t, db):
try:
row = db.query(Tea).filter(Tea.id == id,Tea.is_delete ==False ).update(t)
except:
db.rollback()
return False
else:
if not t:
return 'NO_CHANGE'
obj = db.query(Tea).filter(Tea.id == id, Tea.is_delete == False).first()
if not obj:
return 'NOT_FOUND'
for k, v in t.items():
if hasattr(obj, k):
setattr(obj, k, v)
db.commit()
return row
return 'OK'
except Exception as e:
db.rollback()
print('更新失败原因', repr(e))
return f'ERROR: {e}'
#查询
def get_tea_dao(t,db):
@@ -48,8 +57,8 @@ def get_tea_dao(t,db):
query = query.filter(Tea.sex == t.get('sex') )
if t.get('class_id'):
query = query.filter(Tea.class_id == t.get('class_id') )
if t.get('sub'):
query = query.filter(Tea.sub == t.get('sub') )
if t.get('subject_id'):
query = query.filter(Tea.subject_id == t.get('subject_id') )
if t.get('home_place'):
query = query.filter(Tea.home_place == t.get('home_place') )
if t.get('specialty'):
@@ -65,7 +74,7 @@ def get_tea_dao(t,db):
if t.get('phone'):
query = query.filter(Tea.phone == t.get('phone') )
r = query.all()
return [{'id':i.id,'name':i.name,'class_id':i.class_id,'sub':i.sub,'sex':i.sex,'home_place':i.home_place
return [{'id':i.id,'name':i.name,'class_id':i.class_id,'sub':i.subject_id,'sex':i.sex,'home_place':i.home_place
,'specialty':i.specialty,'start_time':i.start_time,'work_experience':i.work_experience
,'collage':i.collage,'phone':i.phone} for i in r]
@@ -96,8 +105,8 @@ def delete_other_tea_dao(t,db):
query1 = query1.filter(Tea.sex == t.get('sex'))
if t.get('class_id'):
query1 = query1.filter(Tea.class_id == t.get('class_id'))
if t.get('sub'):
query1 = query1.filter(Tea.sub == t.get('sub'))
if t.get('subject_id'):
query1 = query1.filter(Tea.subject_id == t.get('subject_id'))
if t.get('home_place'):
query1 = query1.filter(Tea.home_place == t.get('home_place'))
if t.get('collage'):
@@ -122,3 +131,21 @@ def delete_other_tea_dao(t,db):
def add_subject_dao(t,db):
try:
t1 = db.query(Sub).filter(Sub.name == t.get('name')).first()
if t1:
return'已存在'
s1 = Sub(**t)
db.add(s1)
db.commit()
return '[s1]'
except InterruptedError as e:
db.rollback()
print('失败的原因',e)
return False
except Exception as e:
db.rollback()
return f'{e}'
+1
View File
@@ -24,4 +24,5 @@ def get_db():
yield db
finally:
db.close()
#建表
Base.metadata.create_all(engine1)
+4 -4
View File
@@ -9,13 +9,13 @@ 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=55000)
uvicorn.run('main:app', host='0.0.0.0', port=56000)
"""
IPv4 地址 . . . . . . . . . . . . : 192.168.1.51
子网掩码 . . . . . . . . . . . . : 255.255.255.0
-3
View File
@@ -16,17 +16,14 @@ class Tea(Base_all):
,autoincrement=True
,comment='id')
name = Column(String(50)
,nullable=False
,comment='姓名')
sex = Column(Enum(Gender),default='未知'
,comment='性别')
class_id = Column(Integer
,nullable=False
,comment='班级id')
phone = Column(String(50))
subject_id = Column(Integer
, ForeignKey('subject.id')
,nullable=False
,comment='教学科目')
home_place = Column(String(50)
,comment='家乡')
+10 -7
View File
@@ -1,4 +1,4 @@
from pydantic import BaseModel
from pydantic import BaseModel,Field
from Teachers.model.tea_model import *
#查询使用请求体,都可以为空
@@ -7,8 +7,8 @@ class TeaRequest(BaseModel):
name: str|None = None
sex: Gender|None = None
class_id: int | None = None
sub: str|None = None
phone: str|None = None
subject_id: str|None = None
phone: str | None
home_place: str|None = None
collage: str|None = None
education: str|None = None
@@ -22,9 +22,9 @@ class NewTeaRequest(BaseModel):
name: str | None = None
sex: Gender|None = None
class_id: int | None = None
sub: str | None = None
subject_id: int | None = None
home_place: str | None = None
phone: str
phone: str | None = Field(default=None, min_length=11, max_length=11)
collage: str | None = None
education: str | None = None
work_experience: str | None = None
@@ -35,11 +35,14 @@ class NewTeaRequest1(BaseModel):
name: str | None = None
sex: Gender|None = None
class_id: int | None = None
sub: str | None = None
subject_id: int | None = None
home_place: str | None = None
phone: str|None = None
phone: str | None = Field(default=None, min_length=11, max_length=11)
collage: str | None = None
education: str | None = None
work_experience: str | None = None
start_time: datetime | None = None
specialty: str | None = None
class SubRequest(BaseModel):
name: str
+39 -32
View File
@@ -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})
+47 -58
View File
@@ -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
+2 -3
View File
@@ -11,14 +11,13 @@ DB_HOST = os.getenv("DB_HOST", "localhost")
DB_PORT = os.getenv("DB_PORT", "3306")
DB_NAME = os.getenv("DB_NAME", "student_manage_system")
class_management_rul = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}?charset=utf8mb4"
class_management_url = "mysql+pymysql://root:123456@localhost:3306/student_manage_system?charset=utf8mb4"
engine = create_engine(class_management_rul)
engine = create_engine(class_management_url)
SessionLocal = sessionmaker(bind=engine,autocommit=False, autoflush=False)
Base = declarative_base()
# 获取数据库会话依赖
def get_db():
db = SessionLocal()
try :
+3 -2
View File
@@ -3,6 +3,7 @@ from class_management.api.class_management_api import class_api
from class_management.model.class_management_model import ClassInfo
from class_management.database import Base,engine
# 合并接口
Classes_API = APIRouter()
Classes_API.include_router(class_api)
@@ -12,8 +13,8 @@ app = FastAPI()
app.include_router(class_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=12345)
@@ -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'
+9 -9
View File
@@ -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!")
+33
View File
@@ -0,0 +1,33 @@
#scores模块解释
##项目层
web框架:FastAPI(生成接口)
ORM:sqlalchemy
校验:MYSQL(通过PyMysql驱动)
运行:uvicorn,端口12346
用FastAPI显露HTTP接口 ,对MysQL的scores表进行一个增,删,改,查的动作,删除是软删除,
api是接口层,成绩信息的增加,删除,更新,查询
dao是数据操作层,查询get_scores_dao
更新add_scores_dao
增加update_scores_dao
删除delete_scores_dao
统计查询80分以上的get_top_students_dao
查询连续两次不及格的get_failing_students_dao
班级平均分get_class_avg_dao
model是数据库表模型层,字段信息 id *pk*
sid 链接学生表的学生ID
cid 链接班级表的班级ID
num 考试序次
score 考试成绩
t_subject 链接科目表的科目ID
create_date 创建数据的时间
update_date 更新数据的是时间
is_deleted 逻辑删除
deleted_date 删除的时间
schema是请求体和响应体模型层, 查询的ScoreQuery
更新,增加,删除的ScoreRequest
databases是数据库定义配置文件, 链接引擎对象db_url,链接地址engine,
建表的基类Base,数据库会话管理Session
会话实例def get_db()
main是主程序文件,将配置文件的接口导入,创建接口运行
+1 -1
View File
@@ -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='记录不存在')
+26
View File
@@ -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
+2 -3
View File
@@ -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:
+78
View File
@@ -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
View File
@@ -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)
+5 -5
View File
@@ -15,10 +15,10 @@ def get_stuinfo( #查询学生就业信息
,min_salary:int|None=Query(None,description='最小薪资范围')
,max_salary:int|None=Query(None,description='最大薪资范围')
,somewhere:str|None=Query(None,description='公司地区')
,employment_open_start: date | None = Query(None,description='就业开放起始时间')
,employment_open_end: date | None = Query(None,description='就业开放结束时间')
,offer_recived_start: date | None = Query(None,description='offer下发起始时间')
,offer_recived_end: date | None = Query(None,description='offer下发结束时间')
,employment_open_start: date|None = Query(None,description='就业开放起始时间')
,employment_open_end: date|None = Query(None,description='就业开放结束时间')
,offer_recived_start: date|None = Query(None,description='offer下发起始时间')
,offer_recived_end: date|None = Query(None,description='offer下发结束时间')
,db=Depends(get_db)
): #查询条件
r=get_emp_dao( #调用函数
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
)
+3 -3
View File
@@ -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
+161 -14
View File
@@ -16,48 +16,195 @@ ORM:sqlalchemy
## main:学生管理系统的主程序文件、测试接口
1. 下面所有配置文件的接口全部导入并建表
2. 创建fastapi接口并本地运行
2. 创建合并接口APIRouter
3. 创建fastapi接口并本地运行
## databases:链接数据库配置文件
1. 链接本地mysql,实例化mysql,创建引擎,创建基类,实例化数据库会话
2. 创建函数,调用数据库会话
3. 因为后期多代码合并,每个人的databases都不同链接也不同,所以导入了.env文件
1. 这样在每个人有差异的数据可以直接保存在本地,不用反复同步相互覆盖了
2. 另外一个原因就是model层大家建表要使用同一个基类
1. 所以接入了.env文件随时入参
2. 这样更加方便,而且高频同步和拉取也不用重新配置
## 分层代码书写顺序:
1. model层:
1. 确认你的业务需要哪些字段
2. 确认小组的字段名、表名、库名一致
3. 确认主键、唯一键、字段数据类型、外键等关键字内容
2. 请求体
1. 搞清楚业务需求,需要用到哪些字段
2. 增删改查的具体逻辑
3. api
1. 写好接收变量
2. 装饰器设定
3. 输出规则
4. 一次写好基本上就不会再改了
4. dao
1. 工作量最大的层,所有的业务逻辑都写在这里面
2. 所有的业务逻辑都在这个层里
## api.students_api:接口文件
### 学生表students的功能接口:
1. 学生信息的查询
1. 仅保存入参标量,代码都在逻辑层,建好了基本不用修改,方便维护
2. 学生信息的新增
1. 仅保存入参标量,代码都在逻辑层,建好了基本不用修改,方便维护
3. 学生信息的更新
1. 仅保存入参标量,代码都在逻辑层,建好了基本不用修改,方便维护
4. 学生信息的逻辑删除
1. 仅保存入参标量,代码都在逻辑层,建好了基本不用修改,方便维护
## dao.students_dao:数据筛选文件
### 数据库的逻辑筛选文件:
1. 查询:get_student_dao
1. 筛选所有非空内容,过滤逻辑删除数据,输出学生完整字段
2. 新增:add_student_dao
1. 关键字段限制
2. 过滤各种报错,反馈报错问题
3. 无报错即可全部插入
4. 注意逻辑删除的数据,其实也会占用唯一键,所以有唯一键重复时,直接拉出旧数据进行清空翻新。
3. 更新:update_student_dao
1. 主要过滤掉没有输入的字段,除非输入才可以被覆盖,不能每次更新清空全部内容
2. 唯一字段过滤,比如电话号码,在业务逻辑上电话号码不能重复
4. 逻辑删除:delete_student_dao
1. 逻辑删除可以用所有字段进行精确或者范围删除
2. 但是不可以全为空删除我的整个库
## model.students_model:数据库建表文件
1. 数据表建表代码
2. 字段信息: sid **PK**
, student_name
, student_phone
, class_name
, job_open_time
, offer_issue_time
, company_name
, salary
, create_date
, update_date
, is_deleted
, deleted_date
, **待建FK:** class_id/teacher_id/score_id/company_id/
2. 字段信息: `id`:***PK***
`num`:***UNI***
`name`
`age`
`sex`:*ENUM*
`home_Place`
`college`
`specialty`
`enrollment_time`
`graduate_time`
`education`:*ENUM*
***外键字段***
`class_id`:***FK***
`advisor_id`:***FK***
***额外字段***
`phone`:***UNI***
***通用字段***
`create_date`
`update_date`
`is_deleted`
`deleted_date`
## schema.students_request:请求体、数据过滤
请求体根据业务要求有不同调整,方便后续维护和更改需求
1. 查询:StudentsQuery
1. 所有字段都可以进行查询
2. 随便填哪个都可以进行精确查询或者范围查询
3. 全为空即查询全部
2. 新增:StudentsResponse
1. 新增有必填项、不可重复项等,保证数据干净
3. 更新:StudentsUpdate
1. 以ID作为查询字段,更新其他内容
2. 会过滤掉重复值
4. 逻辑删除:StudentsDelete
1. 所有字段都可以进行删除
2. 随便填哪个都可以进行精确删除或者范围删除
3. 不可全字段为空
## StatCalc:统计分析模块
除了没有model以外没有什么太大的区别
## 学生管理系统修改报错记录
### 1. 合并修改记录:
1. main.py
1. 所有子模块中的main新增了APIRouter,用来拉到主main进行运行
2. stu_jiuye
1. stu_jiuye\main.py
1. 新增为加粗倾斜处
2. from ***sut_jiuye***.文件名 import 调用名
2. CURD_api.py
1. 新增为加粗倾斜处
2. from ***sut_jiuye***.文件名 import 调用名
2. databases.py
1. 所有人的db_url账号密码IP端口和名称全部存入了.env文件
2. 导入了load_dotenv进行调用
1. from dotenv import load_dotenv
2. load_dotenv( )
3. DB_USER = os.getenv("DB_USER", "root")
4. DB_PASSWORD = os.getenv("DB_PASSWORD", "")
5. DB_HOST = os.getenv("DB_HOST", "localhost")
6. DB_PORT = os.getenv("DB_PORT", "3306")
7. DB_NAME = os.getenv("DB_NAME", "student_manage_system")
3. 修改了部分url名称,自行检查
3. class文件夹下导入修改
1. 新增为加粗倾斜处
1. from ***class_management***.文件名 import 调用名
1. 改动文件:
1. class_management_api.py
2. class_management_dao.py
3. class_management_model.py
### 2. 合并完成
1. 新增.env文件,设置好账号密码等
1. 可以一次运行创建好所有表格
2. 但是注意,没办法创建库,也就是student_manage_system
3. 如果没有建库的话会报错
2. 表外键写完之后,可以用SQL语句删除所有表
1. 再重新运行***main.py***文件
1. 这个main.py特指最外层的运行主程序main.py
### 3. 分支测试
1. 阶段完成后
1. 如果不擅长保存版本,可以在自己的分支下面再建新分支
2. 方便切换版本和查询bug,理解逻辑,以免修改后bug更多了
3. 完成后可以将子分支变基到主线
4. 变基(覆盖)完成后可以继续使用子分支进行修改整合
2. 注意云端仓库,没有修改的文件是不会反复上传的
1. 所以看距离时间最近的基本都是被修改过的
2. 可以用来进行版本对比
### 4. 外键创建
1. 外键必须要来自同一个基类,不然会报错
1. 新增主程序的databases.py文件,统一基类
### 5. 外键报错
1. 在新建字段时,依赖主表的外键必须要已经建立,不然在提交时都会报错
2. 所以新增接口的报错逻辑需要特别优化,将各个阶段可能报错的内容都捕获下来
## hermes agent协作内容:
### 1. 测试数据脚本 — init_test_data.py
向数据库插入了完整的测试数据,没有修改任何原有项目文件:
数据类型
数量
科目 6 门(Python程序设计、数据结构、数据库原理等)
教师 6 位(张伟、李芳、王强、陈静、刘洋、赵敏)
班级 3 个(CS2401、CS2402、AI2501)
学生 20 名(涵盖3个班级,男女各半)
成绩 120 条(每名学生6门课,含部分不及格记录)
公司 5 家(字节跳动、阿里巴巴、腾讯、华为、百度)
就业信息 8 条(含薪资、offer时间等)
### 2. 前端页面 — frontend/index.html
单文件完整前端,包含:
首页:问候 Banner + 快捷卡片(查课表/查成绩/校园卡/报修)+ 校园动态信息流
学生管理:表格列表、搜索过滤、新增/编辑/删除(含二次确认弹窗)
成绩查询:学期切换 Tab、成绩卡片展示、Chart.js 柱状图
教师管理:教师列表、搜索、新增
就业管理:就业信息列表、状态标签(已就业/待就业)
个人中心:蓝色背景头像区 + 功能菜单列表
交互特性:
✅ 卡片点击缩放反馈(95% 回弹,150ms)
✅ 页面切换从右侧滑入(300ms)
✅ 删除二次确认弹窗
✅ Toast 提示(成功/失败/信息)
✅ 鼠标悬停按钮/卡片颜色加深、阴影加深
✅ 隔行变色表格 + 表头排序
启动后端后直接浏览器打开 frontend/index.html 即可使用。
+4 -4
View File
@@ -27,15 +27,15 @@ def get_students( stu:StudentsQuery = Depends()
@s_api.post("/students"
,summary='新增学生资料'
,description=f'学生ID自动分配,学生姓名、学生手机号和班级是必填项,不能为空。\n '
f'时间格式:YYYY-MM-DD'
,response_model=StudentsResponse )
f'注意事项:\n'
f'时间格式:YYYY-MM-DD\n'
f'班级ID和顾问ID必须已经存在才可以绑定'
)
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"
+19 -7
View File
@@ -62,9 +62,17 @@ 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不能为空")
s1_class_id = db.query(Students).filter(Students.phone == phone).first()
s1_advisor_id = db.query(Students).filter(Students.phone == phone).first()
if not s1_class_id:
raise ValueError(f"班级ID {s.get("class_id")} 不存在,请先创建班级")
if not s1_advisor_id:
raise ValueError(f"顾问ID {s.get("advisor_id")} 不存在,请先创建顾问")
if s1_deleted and s1_deleted.is_deleted == True:
s1_deleted.name = s.get("name")
s1_deleted.age = s.get("age")
@@ -90,12 +98,16 @@ def add_student_dao( s , db):
return s1_deleted
s1 = Students( **s )
db.add(s1)
except Exception:
db.commit()
except ValueError as e:
# 业务校验异常:回滚并返回错误信息
db.rollback()
return {"code": 400, "msg": str(e)}
except Exception as e:
# 这里可以加日志
db.rollback()
return False
return {"code": 500, "msg": f"系统异常: {str(e)}"}
else:
db.commit()
db.refresh(s1)
return s1
@@ -141,14 +153,14 @@ def update_student_dao( s , db):
# 额外字段
if s.get("phone") is not None:
s1_id.student_phone = s.get("phone")
s1_id.update_date = datetime.now()
db.commit()
except HTTPException:
raise
except Exception:
db.rollback()
raise
else:
s1_id.update_date = datetime.now()
db.commit()
db.refresh(s1_id) # 从数据库再拉一次数据
return s1_id
+4 -3
View File
@@ -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!")
+2 -2
View File
@@ -30,7 +30,7 @@ class Students(Base_all):
, comment='年龄'
, nullable=False)
sex = Column(Enum(SexEnum, values_callable=lambda e: [i.value for i in e])
sex = Column(Enum(SexEnum)
, comment='性别'
, nullable=False)
@@ -49,7 +49,7 @@ class Students(Base_all):
graduate_time = Column(DATETIME
, comment='毕业时间')
education = Column(Enum(EducationEnum, values_callable=lambda e: [i.value for i in e])
education = Column(Enum(EducationEnum)
, comment='学历'
, nullable=True)
+2 -2
View File
@@ -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 = '硕士'