Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f14db433d2 |
Generated
+5
-1
@@ -2,7 +2,11 @@
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.10" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="E:\anaconda3\envs\newai0824" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PackageRequirementsSettings" />
|
||||
<component name="PyDocumentationSettings" />
|
||||
<component name="ReSTService" />
|
||||
<component name="TestRunnerService" />
|
||||
</module>
|
||||
@@ -500,3 +500,66 @@ dev/statistics
|
||||
• 分支管理
|
||||
|
||||
• 合并冲突解决
|
||||
|
||||
|
||||
|
||||
# ER图解析
|
||||
|
||||
都搞定了。ER 图出了概念 / 物理两张,配套把 FastAPI 项目骨架也搭好了,直接下载就能分给 6 个人开工。
|
||||
|
||||
一、ER 图设计要点
|
||||
|
||||

|
||||
|
||||
图1 概念图保留了需求文档里的 stu_name、class_name 冗余字段,用粉色标出,方便答辩时说明"需求这么写";图2 物理图按第三范式把它们去掉了,改用外键关联——这是你们答辩最容易被追问的点,建议这样答:
|
||||
|
||||
冗余字段会造成数据不一致(学生改名后就业表还是旧名字),所以物理建模时去掉,查询时用 JOIN 实时取;只有在校验高频、性能吃紧的场景才按需冗余。
|
||||
|
||||
6 张表 + 1 张关系表:
|
||||
|
||||
表 对应成员 关键设计
|
||||
|
||||
student B is_deleted 逻辑删除、class_id 外键
|
||||
|
||||
class E head_teacher_id 指向班主任
|
||||
|
||||
teacher E 通用老师表
|
||||
|
||||
score C (student_id, exam_no) 联合唯一,防止同一考次重复录入
|
||||
|
||||
employment D student_id 唯一,一人一条就业记录
|
||||
|
||||
class_teacher E 班级↔老师多对多,带 subject 区分授课/带班
|
||||
|
||||
二、项目骨架结构
|
||||
|
||||
|
||||
wolin_student_system/
|
||||
├── requirements.txt
|
||||
├── README.md ← 含分工表 + 启动命令
|
||||
└── app/
|
||||
├── main.py ← A 负责:入口、路由挂载
|
||||
├── config.py ← 数据库 URL(读 .env)
|
||||
├── database.py ← engine + Session
|
||||
├── dependencies.py ← get_db
|
||||
├── exceptions.py ← 统一异常
|
||||
├── models/ ← 6 个成员各占一个文件
|
||||
├── schemas/ ← Pydantic 请求/响应模型
|
||||
├── routers/ ← 各成员主战场(已写好接口骨架+TODO)
|
||||
└── services/ ← 复杂查询放这里
|
||||
|
||||
|
||||
骨架里的贴心细节:
|
||||
• 每个 router 文件里已经把需求文档要求的接口按路径签名写好(参数、装饰器、docstring 齐全),成员只要往里填实现,不用从零对着文档想接口名,避免 6 个人命名风格打架。
|
||||
|
||||
• models 用 SQLAlchemy 2.0 的 Mapped 新写法,relationship 已配好双向关联(比如 student.scores、class.students)。
|
||||
|
||||
• schemas/common.py 提供了 PageResponse 分页包装和 ApiResponse 统一返回结构,全组复用,前端解析方便。
|
||||
|
||||
三、给组长的合并建议
|
||||
|
||||
1. 你先把 dev/base 推上去,其他人 rebase 而不是 merge 你的更新,历史更干净。
|
||||
2. 约定好冲突高发区:app/main.py(路由挂载)和 app/models/__init__.py(模型导出)这两个文件让各成员只改自己那一行,别整文件覆盖。
|
||||
3. routers/__init__.py 和 models/__init__.py 建议由你统一维护,别人提 PR 时只改自己的 router 文件。
|
||||
|
||||
需要我接着做哪个?比如:把统计模块的 SQL 全部写出来、补一份 .env.example + Alembic 迁移配置、或者按这个骨架把某个模块(比如成绩管理)的实现代码直接填满。
|
||||
@@ -1,75 +0,0 @@
|
||||
from datetime import date
|
||||
|
||||
from fastapi import APIRouter,Depends,HTTPException,Query
|
||||
from dao.class_management_dao import add_class_dao, update_class_dao, get_class_dao, delete_class_dao
|
||||
from database import get_db
|
||||
from schema.class_management_request import ClassCreate, ClassResponse, ClassUpdate
|
||||
|
||||
class_api = APIRouter(tags=['班级管理模块'])
|
||||
|
||||
|
||||
|
||||
@class_api.post("/classes",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)
|
||||
|
||||
|
||||
@class_api.get('/classes/{class_id}',summary='根据id查询班级')
|
||||
def get_one_class(class_id:int,db=Depends(get_db)):
|
||||
res = get_class_dao(c={'class_id':class_id},db=db)
|
||||
if not res:
|
||||
raise HTTPException(status_code=404, detail="该班级不存在或已删除")
|
||||
return ClassResponse(data=res)
|
||||
|
||||
@class_api.get('/classes',summary='多字段查询班级')
|
||||
def get_some_classes(class_name:str|None = Query(None,description='班级名称')
|
||||
,class_teacher:str|None = Query(None,description='班主任')
|
||||
,class_lecturer: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='每页信息条数')
|
||||
,db=Depends(get_db)
|
||||
):
|
||||
c_dict={'class_name':class_name
|
||||
,'class_teacher':class_teacher
|
||||
,'class_lecturer':class_lecturer
|
||||
,'class_start_time':class_start_time
|
||||
,'class_end_time':class_end_time
|
||||
}
|
||||
c_dict={k:v for k,v in c_dict.items() if v is not None}#过滤掉值为None的key
|
||||
|
||||
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]
|
||||
|
||||
page_response = ClassResponse(data=page_data,totals=total)
|
||||
return page_response
|
||||
|
||||
|
||||
|
||||
|
||||
@class_api.put("/classes/{class_id}",summary='修改班级信息')
|
||||
def update_class(class_id,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": class_id})
|
||||
|
||||
|
||||
@class_api.delete('/classes/{class_id}',summary='逻辑删除班级')
|
||||
def remove_class(class_id:int,db=Depends(get_db)):
|
||||
del_rows = delete_class_dao(class_id,db)
|
||||
if del_rows <= 0:
|
||||
raise HTTPException(status_code=404, detail="班级不存在,删除失败")
|
||||
return ClassResponse(detail='删除成功',data={'class_id':class_id})
|
||||
@@ -1,81 +0,0 @@
|
||||
from model.class_management_model import ClassInfo
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
#查询
|
||||
def get_class_dao(c:dict,db:Session) :
|
||||
q = db.query(ClassInfo).filter(ClassInfo.is_delete == 0)#is_delete=0代表未删除
|
||||
if c.get('class_id'):
|
||||
q = q.filter(ClassInfo.class_id == c.get('class_id'))
|
||||
if c.get('class_name'):
|
||||
q = q.filter(ClassInfo.class_name == c.get('class_name'))
|
||||
if c.get('class_teacher'):
|
||||
q = q.filter(ClassInfo.class_teacher == c.get('class_teacher'))
|
||||
if c.get('class_lecturer'):
|
||||
q = q.filter(ClassInfo.class_lecturer == c.get('class_lecturer'))
|
||||
if c.get('class_start_time'):
|
||||
q = q.filter(ClassInfo.class_start_time == c.get('class_start_time'))
|
||||
if c.get('class_end_time'):
|
||||
q = q.filter(ClassInfo.class_end_time == c.get('class_end_time'))
|
||||
c1 = q.all()
|
||||
if not c1:
|
||||
return []
|
||||
return[
|
||||
{'班级编号':i.class_id
|
||||
,'班级名称':i.class_name
|
||||
,'班主任':i.class_teacher
|
||||
,'授课老师':i.class_lecturer
|
||||
,'开课时间':i.class_start_time
|
||||
,'结课时间':i.class_end_time
|
||||
} for i in c1
|
||||
]
|
||||
|
||||
|
||||
#新增
|
||||
def add_class_dao(c:dict,db:Session):
|
||||
try:
|
||||
exist_class = db.query(ClassInfo)\
|
||||
.filter(ClassInfo.class_name == c['class_name']
|
||||
,ClassInfo.is_delete == 0)\
|
||||
.first()
|
||||
if exist_class:
|
||||
msg = '班级名称已存在'
|
||||
print(msg)
|
||||
return [False,msg]
|
||||
c1 = ClassInfo(**c)
|
||||
db.add(c1)
|
||||
except:
|
||||
db.rollback()
|
||||
return False
|
||||
else:
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
#修改
|
||||
def update_class_dao(cid:int,update_data,db):
|
||||
try:
|
||||
rows = db.query(ClassInfo)\
|
||||
.filter(ClassInfo.class_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.class_id == cid,ClassInfo.is_delete==0)\
|
||||
.update({'is_delete':1})
|
||||
except:
|
||||
db.rollback()
|
||||
rows = 0
|
||||
else:
|
||||
db.commit()
|
||||
finally:
|
||||
return rows
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker,declarative_base
|
||||
|
||||
class_management_rul = "mysql+pymysql://root:123456@localhost:3306/ai0824_cyf?charset=utf8mb4"
|
||||
|
||||
engine = create_engine(class_management_rul)
|
||||
SessionLocal = sessionmaker(bind=engine,autocommit=False, autoflush=False)
|
||||
Base = declarative_base()
|
||||
|
||||
# 获取数据库会话依赖
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try :
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
from fastapi import FastAPI,APIRouter
|
||||
from api.class_management_api import class_api
|
||||
from model.class_management_model import ClassInfo
|
||||
from database import Base,engine
|
||||
import model
|
||||
app = FastAPI()
|
||||
|
||||
app.include_router(class_api)
|
||||
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
if __name__ =='__main__':
|
||||
import uvicorn
|
||||
uvicorn.run('main:app',host='127.0.0.1',port=12345)
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
from sqlalchemy import Column,Integer,String,Date,Boolean
|
||||
from sqlalchemy.dialects.mysql import DATETIME
|
||||
from datetime import datetime
|
||||
from database import Base
|
||||
|
||||
|
||||
|
||||
|
||||
class ClassInfo(Base):
|
||||
__tablename__ = 'class_info'
|
||||
|
||||
class_id = Column(Integer
|
||||
,primary_key=True
|
||||
,autoincrement=True
|
||||
,comment='班级id主键'
|
||||
)
|
||||
class_name = Column(String(10)
|
||||
,nullable=False
|
||||
,comment='班级名称'
|
||||
)
|
||||
class_teacher = Column(String(50)
|
||||
,nullable=False
|
||||
,comment='班主任'
|
||||
)
|
||||
class_lecturer = Column(String(50)
|
||||
,nullable=False
|
||||
,comment='授课老师'
|
||||
)
|
||||
class_start_time = Column(Date
|
||||
,nullable=False
|
||||
,comment='开班日期'
|
||||
)
|
||||
class_end_time = Column(Date
|
||||
,nullable=False
|
||||
,comment='结课日期'
|
||||
)
|
||||
is_delete = Column(Integer
|
||||
,default=0
|
||||
,comment='逻辑删除 0未删除 1已删除'
|
||||
)
|
||||
create_time = Column(DATETIME
|
||||
,default=datetime.now
|
||||
,comment='记录创建时间'
|
||||
)
|
||||
update_time = Column(DATETIME
|
||||
,default=datetime.now
|
||||
,onupdate=datetime.now
|
||||
,comment='记录更新时间'
|
||||
)
|
||||
@@ -1,42 +0,0 @@
|
||||
from pydantic import BaseModel,Field
|
||||
from datetime import date,datetime
|
||||
from typing import Optional,List
|
||||
|
||||
#基础班级请求体
|
||||
class ClassBase(BaseModel):
|
||||
class_teacher:str|None = Field(None,description='班主任')
|
||||
class_lecturer:str|None = Field(None,description='授课老师')
|
||||
class_start_time:date|None = Field(None,description='开班日期')
|
||||
class_end_time:date|None = Field(None,description='结课日期')
|
||||
|
||||
#新增班级请求体模型
|
||||
class ClassCreate(ClassBase):
|
||||
class_name:str = Field(..., description='班级名称')
|
||||
|
||||
#修改班级请求体模型
|
||||
class ClassUpdate(ClassBase):
|
||||
class_name:str = Field(None,description='班级名称')
|
||||
|
||||
|
||||
class ClassInfoResp(BaseModel):
|
||||
class_id:int
|
||||
class_name:str
|
||||
class_teacher:str
|
||||
class_lecturer:str
|
||||
class_start_time:date
|
||||
class_end_time:date
|
||||
create_time:datetime
|
||||
update_time:datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ClassResponse(BaseModel):
|
||||
code:int = 200
|
||||
detail:str = 'OK'
|
||||
totals:int = 0
|
||||
data:str|dict|tuple|list
|
||||
msg:str = 'OK'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user