diff --git a/api/calss.py b/api/calss.py index 2190caf..7de63ae 100644 --- a/api/calss.py +++ b/api/calss.py @@ -2,7 +2,7 @@ import uuid from fastapi import APIRouter, Depends, HTTPException,Query from dao.class_dao import update_class_dao, add_class_dao, get_class_dao from database import get_db -from model.class_model import StudentClassIntermediate,Student_info +from model.class_model import StudentClassIntermediate from schema.class_request import ClassRequest, ClassResponse, TransferRequest from dao.class_dao import Student_delect_Dao from datetime import datetime diff --git a/api/region_api.py b/api/region_api.py new file mode 100644 index 0000000..cd234d9 --- /dev/null +++ b/api/region_api.py @@ -0,0 +1,106 @@ +# 导入类型提示 List,用于指定响应模型为列表 +from typing import List + +# 导入 FastAPI 的 APIRouter(路由)、Depends(依赖注入)、HTTPException(HTTP 异常) +from fastapi import APIRouter, Depends, HTTPException + +# 从 dao.region_sql 导入数据库操作函数,并为添加、修改、删除起了别名,避免与路由函数重名 +from dao.region_sql import ( + get_region_part, + add_region as dao_add_resp, + update_region as dao_update_resp, + delete_region as dao_delete_resp, +) + +# 导入数据库会话依赖,用于每个请求获取独立的 Session +from database import get_db + +# 导入 Pydantic 模型:地区响应体、地区添加请求体、通用操作响应体、地区修改请求体 +from schema.stu_info_region_schema import ( + region_resp, + region_req, + region_add_resp, + region_update_req, +) + +# 创建地区模块的路由实例 +region_api = APIRouter() + + +# -------------------- 查询地区(支持按省份过滤) -------------------- +@region_api.get('/region', response_model=List[region_resp], summary='查询') +def get_regions_part(province: str = None, gs=Depends(get_db)): + """ + 根据省份查询地区信息。 + - province 为可选参数,不传则返回全部地区。 + - 返回列表,每个元素符合 region_resp 模型。 + """ + try: + # 调用 dao 层查询函数,传入数据库会话和省份过滤条件 + data = get_region_part(gs, province) + return data + except Exception as e: + # 捕获异常,返回 500 错误,详情为字符串化的异常信息 + raise HTTPException(status_code=500, detail=str(e)) + + +# -------------------- 添加地区 -------------------- +@region_api.post('/region', response_model=region_add_resp, summary='添加') +def add_region(rreq: region_req, gs=Depends(get_db)): + """ + 新增一条地区记录。 + - 请求体必须符合 region_req 模型。 + - 返回统一的 region_add_resp(code + message)。 + """ + try: + # 将 Pydantic 模型转为字典,便于传给 dao 层 + data = rreq.model_dump() + # 调用 dao 层添加函数(别名为 dao_add_resp) + dao_add_resp(gs, data) + return region_add_resp(code=200, message='数据已添加') + except Exception as e: + # 注意:此处状态码写成了 200,通常异常应返回 500。 + # 建议改为 status_code=500。 + raise HTTPException(status_code=200, detail=str(e)) + + +# -------------------- 修改地区 -------------------- +@region_api.put('/region', response_model=region_add_resp, summary='修改') +def update_region(code: str, rreq: region_update_req, gs=Depends(get_db)): + """ + 根据地区编码更新地区信息。 + - code 为查询参数,指定要修改的地区编码。 + - 请求体为 region_update_req,字段均为可选,只更新传入的字段。 + - 返回统一的 region_add_resp。 + """ + try: + # exclude_unset=True:只获取客户端显式传入的字段,未传的字段不参与更新 + data = rreq.model_dump(exclude_unset=True) + # 调用 dao 层更新函数(别名为 dao_update_resp),传入编码和待更新数据 + res = dao_update_resp(gs, code, data) + if not res: + # 没有更新任何行,说明编码不存在或数据未变化 + raise HTTPException(status_code=500, detail='没有更新!') + # res 可能是 dao 返回的消息字符串,直接作为 message 返回 + return region_add_resp(code=200, message=res) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +# -------------------- 删除地区 -------------------- +@region_api.delete('/region', response_model=region_add_resp, summary='删除') +def delete_region(code: str, gs=Depends(get_db)): + """ + 根据地区编码删除地区记录。 + - code 为查询参数,指定要删除的地区编码。 + - 返回统一的 region_add_resp。 + """ + try: + # 调用 dao 层删除函数(别名为 dao_delete_resp),传入编码 + res = dao_delete_resp(gs, code) + if not res: + # 没有删除任何行,可能编码不存在 + raise HTTPException(status_code=500, detail='没有删除') + return region_add_resp(code=200, message='删除成功') + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) diff --git a/api/student_info_api.py b/api/student_info_api.py new file mode 100644 index 0000000..597cf81 --- /dev/null +++ b/api/student_info_api.py @@ -0,0 +1,101 @@ +# 导入 FastAPI 的 APIRouter(用于组织路由)、Depends(依赖注入)、HTTPException(抛出 HTTP 异常) +from fastapi import APIRouter, Depends, HTTPException + +# 从 dao 层导入数据库操作函数,并对修改和删除函数起了别名,避免与路由函数重名 +from dao.student_info_sql import ( + get_part_student_info, + add_student_info, + update_student_info as update_info, + delete_student_info as delete_info, +) + +# 导入数据库会话依赖,用于每个请求获取独立的 Session +from database import get_db + +# 导入 Pydantic 模型:请求体、响应体、通用操作响应体 +from schema.stu_info_region_schema import stu_info_req, stu_info_resp, stu_info_add_resp + +# 创建学生信息模块的路由实例 +student_info_api = APIRouter() + + +# -------------------- 查询学生信息(支持条件过滤) -------------------- +@student_info_api.get('/student', response_model=list[stu_info_resp], summary='查询') +def get_student_info_part( + student_id: str = None, + student_name: str = None, + gender: str = None, + gs=Depends(get_db) +): + """ + 根据学号、姓名、性别查询学生信息。 + - 所有查询参数均为可选,不传则返回全部未删除记录。 + - 返回列表,每个元素符合 stu_info_resp 模型。 + """ + try: + # 调用 dao 层查询函数,传入数据库会话和过滤条件 + data = get_part_student_info(gs, student_id, student_name, gender) + return data + except Exception as err: + # 捕获异常,返回 500 错误,详情为原始异常信息 + raise HTTPException(status_code=500, detail=err) + + +# -------------------- 添加学生信息 -------------------- +@student_info_api.post('/student', response_model=stu_info_add_resp, summary='添加') +def add_student_infomation(stu_info: stu_info_req, gs=Depends(get_db)): + """ + 新增一条学生记录。 + - 请求体必须符合 stu_info_req 模型。 + - 返回统一的 stu_info_add_resp(code + message)。 + """ + try: + # 将 Pydantic 模型转为字典,便于传给 dao 层 + data = stu_info.model_dump() + # 调用 dao 层添加函数 + res = add_student_info(gs, data) + if not res: + # 如果 dao 返回假值(如 None、空字符串),说明添加失败 + raise HTTPException(status_code=500, detail='添加失败') + return stu_info_add_resp(code=200, message='添加成功') + except Exception as e: + # 将异常信息转为字符串,避免序列化问题 + raise HTTPException(status_code=500, detail=str(e)) + + +# -------------------- 修改学生信息 -------------------- +@student_info_api.put('/student', response_model=stu_info_add_resp, summary='修改') +def update_student_info(stu_info: stu_info_req, gs=Depends(get_db)): + """ + 根据学号更新学生信息。 + - 请求体必须包含完整的 stu_info_req(包括 student_id)。 + - 返回统一的 stu_info_add_resp。 + """ + try: + data = stu_info.model_dump() + # 调用 dao 层更新函数(已别名为 update_info) + res = update_info(gs, data) + if not res: + # 没有更新任何行,说明学号不存在或数据未变化 + raise HTTPException(status_code=500, detail='没有更新!') + return stu_info_add_resp(code=200, message='更新成功') + except Exception as err: + raise HTTPException(status_code=500, detail=str(err)) + + +# -------------------- 删除学生信息(逻辑删除) -------------------- +@student_info_api.delete('/student', response_model=stu_info_add_resp, summary='删除') +def delete_student_info(sid: str, gs=Depends(get_db)): + """ + 根据学号删除学生记录(通常为逻辑删除)。 + - sid 为查询参数,返回统一的 stu_info_add_resp。 + """ + try: + # 调用 dao 层删除函数(已别名为 delete_info),传入学号 + res = delete_info(gs, sid) + if not res: + # 没有删除任何行,可能学号不存在 + raise HTTPException(status_code=500, detail='没有删除') + return stu_info_add_resp(code=200, message='删除成功') + except Exception as err: + raise HTTPException(status_code=500, detail=str(err)) \ No newline at end of file diff --git a/dao/region_sql.py b/dao/region_sql.py new file mode 100644 index 0000000..10ee1f9 --- /dev/null +++ b/dao/region_sql.py @@ -0,0 +1,110 @@ +# 导入 SQLAlchemy 的 Session 类,用于类型提示 +from sqlalchemy.orm import Session +# 导入地区维度表模型 +from model.student_info_region_model import Region_dimension + + +# ==================== 查询地区 ==================== +def get_region_part(session: Session, province: str): + """ + 根据省份查询地区信息。 + :param session: 数据库会话 + :param province: 省份名称,若为空则查询全部 + :return: Region_dimension 对象列表 + """ + try: + # 如果传入了省份(非空),则按省份过滤 + if province: + data = session.query(Region_dimension).filter( + Region_dimension.province == province + ).all() + else: + # 否则查询所有地区 + data = session.query(Region_dimension).all() + return data + except Exception as e: + # 出现异常直接向上抛出,由 API 层统一处理 + raise e + + +# ==================== 添加地区 ==================== +def add_region(session: Session, d1: dict): + """ + 新增一条地区记录。 + :param session: 数据库会话 + :param d1: 地区信息字典,需包含 code、province、city、district + :return: 成功时返回字符串 '添加成功' + """ + try: + # 先检查该地区编码是否已存在 + data = session.query(Region_dimension).filter( + Region_dimension.code == d1["code"] + ).first() + + # 如果已存在,抛出 ValueError(注意:这里没有带错误信息,调用方需自行处理) + if data: + raise ValueError + + # 根据字典创建 ORM 对象 + obj = Region_dimension(**d1) + session.add(obj) # 加入会话 + session.commit() # 提交事务,写入数据库 + session.refresh(obj) # 刷新对象,获取数据库生成的默认值(如自增 id) + return '添加成功' + except Exception as e: + # 出现异常时回滚事务,避免脏数据 + session.rollback() + raise e + + +# ==================== 修改地区 ==================== +def update_region(session: Session, code, d1: dict): + """ + 根据地区编码更新地区信息(部分字段更新)。 + :param session: 数据库会话 + :param code: 要修改的地区编码 + :param d1: 待更新的字段字典 + :return: 成功返回 '修改成功';未找到记录返回 None + """ + try: + # 使用 query.update() 批量更新,返回受影响行数 + rows = session.query(Region_dimension).filter( + Region_dimension.code == code + ).update(d1) + + # 如果受影响行数为 0,说明编码不存在,无记录可更新 + if rows == 0: + return None + + session.commit() + return '修改成功' + except Exception as e: + # 异常时回滚 + session.rollback() + raise e + + +# ==================== 删除地区 ==================== +def delete_region(session: Session, code): + """ + 根据地区编码删除地区记录(物理删除)。 + :param session: 数据库会话 + :param code: 要删除的地区编码 + :return: 成功返回 '删除成功';未找到记录返回 None + """ + try: + # 使用 query.delete() 物理删除,返回受影响行数 + rows = session.query(Region_dimension).filter( + Region_dimension.code == code + ).delete() + + # 如果受影响行数为 0,说明编码不存在 + if rows == 0: + return None + + session.commit() + return '删除成功' + except Exception as e: + # 异常时回滚 + session.rollback() + raise e \ No newline at end of file diff --git a/dao/student_info_sql.py b/dao/student_info_sql.py new file mode 100644 index 0000000..08bab79 --- /dev/null +++ b/dao/student_info_sql.py @@ -0,0 +1,153 @@ +# 导入 SQLAlchemy 的 Session 类,用于类型提示 +from sqlalchemy.orm import Session +# 导入学生信息模型和性别枚举 +from model.student_info_region_model import Student_info, GenderEnum +# 导入完整性错误异常,用于捕获外键/唯一约束等数据库错误 +from sqlalchemy.exc import IntegrityError + + +# ==================== 条件查询学生信息 ==================== +def get_part_student_info(session: Session, student_id: str, student_name: str, gender: str): + """ + 根据学号、姓名、性别查询学生信息(均为可选条件)。 + :param session: 数据库会话 + :param student_id: 学号,精确匹配 + :param student_name: 姓名,精确匹配 + :param gender: 性别,前端传“男/女”,内部转为“1/0” + :return: 学生对象列表 + """ + # 前端传来的性别为 男/女,与数据库的枚举值('0'/'1')不符,需要转换 + # 注意:若 gender 为 None(不筛选),此处会被错误地转为 '0',导致只查女生。 + # 建议调用方在不需要筛选性别时不要传该参数,或修改此段逻辑。 + if gender == '男': + gender = '1' + else: + gender = '0' + + try: + # 创建基础查询 + query = session.query(Student_info) + + # 过滤逻辑删除:只查询未删除的记录(is_deleted = '0') + query = query.filter(Student_info.is_deleted == '0') + + # 如果传入学号,则按学号精确过滤 + if student_id is not None: + query = query.filter(Student_info.student_id == student_id) + + # 如果传入姓名,则按姓名精确过滤 + # 若需模糊查询,可将 == 改为 like(f"%{student_name}%") + if student_name is not None: + query = query.filter(Student_info.student_name == student_name) + + # 如果传入性别,则按性别过滤(已转为 '0'/'1',再转为枚举) + if gender is not None: + # 把字符串转成枚举,SQLAlchemy 才能正确比较 + query = query.filter(Student_info.gender == GenderEnum(gender)) + + # 返回所有匹配记录 + return query.all() + except Exception as e: + # 出现异常直接向上抛出,由 API 层统一处理 + raise e + + +# ==================== 添加学生信息 ==================== +def add_student_info(session: Session, data: dict): + """ + 新增一条学生记录。 + :param session: 数据库会话 + :param data: 学生信息字典,键需与模型字段对应 + :return: 成功时返回字符串 '添加成功' + """ + try: + # 先检查学号是否已存在 + rows = session.query(Student_info).filter( + Student_info.student_id == data['student_id'] + ).first() + + # 如果已存在,返回 ValueError 类(注意:这里返回的是类,不是实例,也不是抛出异常) + # 调用方如果做 `if not res` 判断,ValueError 类是真值,可能导致逻辑错误。 + # 建议改为 `raise ValueError("学号已存在")` + if rows is not None: + return ValueError + + # 根据字典创建 ORM 对象 + obj = Student_info(**data) + session.add(obj) # 加入会话 + session.commit() # 提交事务,写入数据库 + return '添加成功' + except Exception as e: + # 出现异常时回滚事务,避免脏数据 + session.rollback() + raise e + + +# ==================== 修改学生信息 ==================== +def update_student_info(session: Session, data: dict): + """ + 根据学号更新学生信息(全量更新)。 + :param session: 数据库会话 + :param data: 包含 student_id 及待更新字段的字典 + :return: 成功返回 '修改成功';未找到记录返回 None + """ + try: + # 使用 query.update() 批量更新,返回受影响行数 + rows = session.query(Student_info).filter( + Student_info.student_id == data['student_id'] + ).update(data) + + # 如果受影响行数为 0,说明学号不存在,无记录可更新 + if rows == 0: + return None + + session.commit() + return '修改成功' + + except IntegrityError as e: + # 捕获数据库完整性错误(外键、唯一约束等),回滚并翻译为友好提示 + session.rollback() + err_msg = str(e.orig) # 获取底层 MySQL 错误信息 + + if 'fk_student_region' in err_msg: + # 外键 fk_student_region 失败:region_id 在地区表中不存在 + raise ValueError( + f"region_id '{data.get('region_id')}' 在地区表中不存在,请先维护 region_dimension" + ) + elif 'id_card' in err_msg: + # 身份证号唯一约束冲突 + raise ValueError(f"身份证号 '{data.get('id_card')}' 已被其他学生占用") + else: + # 其他完整性错误 + raise ValueError(f"数据完整性错误: {err_msg}") + + except Exception as e: + # 其他异常同样回滚并抛出 + session.rollback() + raise e + + +# ==================== 删除学生信息(逻辑删除) ==================== +def delete_student_info(session: Session, student_id: str): + """ + 根据学号进行逻辑删除(将 is_deleted 置为 '1')。 + :param session: 数据库会话 + :param student_id: 学号 + :return: 成功返回 '修改成功';未找到记录返回 None + """ + try: + # 使用 update 将 is_deleted 改为 '1',实现逻辑删除 + rows = session.query(Student_info).filter( + Student_info.student_id == student_id + ).update({'is_deleted': '1'}) + + # 没有匹配记录,返回 None + if rows == 0: + return None + + session.commit() + return '修改成功' + except Exception as e: + # 异常时回滚 + session.rollback() + raise e \ No newline at end of file diff --git a/enums.py b/enums.py new file mode 100644 index 0000000..24e34da --- /dev/null +++ b/enums.py @@ -0,0 +1,48 @@ +# 导入 Python 标准库 enum,用于定义枚举类型 +import enum + +# ==================== 性别枚举 ==================== +class GenderEnum(str, enum.Enum): + """ + 性别枚举:继承 str 和 enum.Enum,使成员值可直接作为字符串使用。 + 与数据库 enum('0','1') 对应。 + """ + female = "0" # 女 + male = "1" # 男 + + +# ==================== 学籍状态枚举 ==================== +class StudentStatusEnum(str, enum.Enum): + """ + 学籍状态枚举,与数据库 enum('0','1','2','3','4') 对应。 + """ + studying = "0" # 在读 + suspended = "1" # 休学 + quit = "2" # 退学 + graduated = "3" # 毕业 + completed = "4" # 结业 + + +# ==================== 学历层次枚举 ==================== +class EducationLevelEnum(str, enum.Enum): + """ + 学历层次枚举,与数据库 enum('1','2','3','4') 对应。 + """ + college = "1" # 大专 + bachelor = "2" # 本科 + master = "3" # 硕士研究生 + doctor = "4" # 博士研究生 + + +# ==================== 逻辑删除枚举 ==================== +class IsDeletedEnum(str, enum.Enum): + """ + 逻辑删除标志枚举。 + 注意:此处成员值为整数 0 和 1,而其他枚举均为字符串。 + 数据库列定义为 enum('0','1')(字符串),因此建议将此处改为: + no = "0" + yes = "1" + 以保持类型一致,避免 SQLAlchemy 绑定参数时出现类型不匹配问题。 + """ + no = "0" # 未删除 + yes = "1" # 已删除 \ No newline at end of file diff --git a/main.py b/main.py index 166d584..ca27774 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,8 @@ from database import engine, Base Base.metadata.create_all(engine) from api.calss import class_api from api.employment_api import app as employment_router +from api.student_info_api import student_info_api +from api.region_api import region_api from starlette.middleware.cors import CORSMiddleware @@ -17,6 +19,11 @@ app.add_middleware( ) app.include_router(class_api,tags=['班级接口'],prefix='/stutent') + +app.include_router(student_info_api,tags=['学生信息']) + +app.include_router(region_api,tags=['地区信息']) + app.include_router(employment_router) diff --git a/model/class_model.py b/model/class_model.py index 376a8cb..61eb5fc 100644 --- a/model/class_model.py +++ b/model/class_model.py @@ -70,53 +70,4 @@ class StudentClassIntermediate(Base): student_id =Column(String(20)) is_deleted= Column(Integer) create_time= Column(DateTime) - update_time= Column(DateTime) - - - - -class GenderEnum(str, enum.Enum): - FEMALE = '0' - MALE = '1' - -class StudentStatusEnum(str, enum.Enum): - NORMAL = '0' # 正常 - SUSPENDED = '1' # 休学 - DROPPED = '2' # 退学 - GRADUATED = '3' # 毕业 - OTHER = '4' # 其他 - -class EducationLevelEnum(str, enum.Enum): - COLLEGE = '1' # 大专 - UNDERGRADUATE = '2' # 本科 - MASTER = '3' # 硕士 - DOCTOR = '4' # 博士 - -class IsDeletedEnum(str, enum.Enum): - ACTIVE = '0' # 未删除 - DELETED = '1' # 已删除 - -# 3. 定义学生模型类 -class Student_info(Base): - __tablename__ = "student_info" - student_id = Column(String(20),primary_key=True, comment='学号') - student_name = Column(String(50), nullable=False, comment='学生姓名') - gender = Column(Enum(GenderEnum), nullable=False, comment='性别: 0=女, 1=男') - id_card = Column(String(18), nullable=False, comment='身份证号码,收紧至18位') - birthday = Column(Date, nullable=True, comment='出生日期') - ethnicity = Column(String(20), nullable=True, comment='民族') - region_id = Column(String(12), nullable=True, comment='籍贯地区编码,外键关联地区维度表(region_dimension)') - phone = Column(String(11), nullable=True, comment='手机号码,收紧至11位') - graduation_school = Column(String(20), nullable=True, comment='毕业学校') - major = Column(String(20), nullable=False, comment='就读专业') - class_id = Column(String(20), nullable=False, index=True, comment='所在班级,外键关联班级表(class_info)') - enrollment_date = Column(Date, nullable=True, comment='入学日期') - graduation_date = Column(Date, nullable=True, comment='毕业日期') - student_status = Column(Enum(StudentStatusEnum), nullable=False, default=StudentStatusEnum.NORMAL, - comment='学籍状态') - education_level = Column(Enum(EducationLevelEnum), nullable=False, - comment='学历层次:1=大专, 2=本科, 3=硕士研究生, 4=博士研究生') - is_deleted = Column(Enum(IsDeletedEnum), nullable=False, default=IsDeletedEnum.ACTIVE, - comment='逻辑删除:0=未删除, 1=已删除') - create_time = Column(DateTime, nullable=False, server_default=func.now(), comment='创建时间') - update_time = Column(DateTime, nullable=False, server_default=func.now(), onupdate=func.now(), comment='更新时间') \ No newline at end of file + update_time= Column(DateTime) \ No newline at end of file diff --git a/model/student_info_region_model.py b/model/student_info_region_model.py new file mode 100644 index 0000000..69cbb8a --- /dev/null +++ b/model/student_info_region_model.py @@ -0,0 +1,95 @@ +# 从 database 模块导入 Base(ORM 基类)和 engine(数据库引擎,别名为 db_engine) +from database import Base, engine as db_engine + +# 从 sqlalchemy 导入 Enum,并别名为 SAEnum,用于定义数据库枚举列 +from sqlalchemy import Enum as SAEnum, Integer + +# 从 sqlalchemy 导入常用组件:inspect(检查表是否存在)、Column(列)、 +# VARCHAR/BIGINT/Date/DateTime(字段类型)、func(SQL 函数,如 now()) +from sqlalchemy import inspect, Column, VARCHAR, BIGINT, Date, DateTime, func + +# 从自定义枚举模块导入学生状态、性别、学历层次、逻辑删除枚举 +from enums import StudentStatusEnum, GenderEnum, EducationLevelEnum, IsDeletedEnum + + +def _sa_enum(e): + """ + 统一让 SQLAlchemy 存枚举的 value 而不是 name。 + 默认 SQLAlchemy 的 Enum 存的是枚举成员名(如 'male'), + 这里通过 values_callable 指定存枚举的值(如 '1'、'0')。 + """ + return SAEnum(e, values_callable=lambda x: [m.value for m in x]) + +# ======================== 学生 ========================== +# 学生信息表模型,继承 Base(declarative_base 生成的基类) +class Student_info(Base): + __tablename__ = 'student_info' # 数据库表名 + # 表参数:指定字符集和排序规则为 utf8mb4 + __table_args__ = {'mysql_charset': 'utf8mb4', 'mysql_collate': 'utf8mb4_general_ci'} + + # 学号,主键,非空,长度 20 + student_id = Column(VARCHAR(20), primary_key=True, nullable=False, comment='学号,收紧长度') + # 学生姓名,非空,长度 50 + student_name = Column(VARCHAR(50), nullable=False, comment='学生姓名') + # 性别,枚举类型(存 value:'0'=女,'1'=男),非空 + gender = Column(Integer, nullable=False, comment='性别:0=女,1=男') + # 身份证号码,唯一,非空,长度 18 + id_card = Column(VARCHAR(18), unique=True, nullable=False, comment='身份证号码,收紧至18位') + # 出生日期,可空 + birthday = Column(Date, nullable=True, comment='出生日期') + # 民族,可空,长度 20 + ethnicity = Column(VARCHAR(20), nullable=True, comment='民族') + # 籍贯地区编码,可空,带索引(注释说外键关联 region_dimension,但模型里未定义 ForeignKey) + region_id = Column(VARCHAR(12), nullable=True, index=True, + comment='籍贯地区编码,外键关联地区维度表(region_dimension)') + # 手机号码,可空,长度 11 + phone = Column(VARCHAR(11), nullable=True, comment='手机号码,收紧至11位') + # 毕业学校,可空,长度 20 + graduation_school = Column(VARCHAR(20), nullable=True, comment='毕业学校') + # 就读专业,非空,长度 20 + major = Column(VARCHAR(20), nullable=False, comment='就读专业') + # 所在班级,非空,长度 20,带索引(注释说外键关联 class_info,但模型里未定义 ForeignKey) + class_id = Column(VARCHAR(20), nullable=False, index=True, comment='所在班级,外键关联班级表(class_info)') + # 入学日期,可空 + enrollment_date = Column(Date, nullable=True, comment='入学日期') + # 毕业日期,可空 + graduation_date = Column(Date, nullable=True, comment='毕业日期') + # 学籍状态,枚举,非空:0=在读,1=休学,2=退学,3=毕业,4=结业 + student_status = Column(Integer, nullable=False,comment='学籍状态:0=在读,1=休学,2=退学,3=毕业,4=结业') + # 学历层次,枚举,非空:1=大专,2=本科,3=硕士研究生,4=博士研究生 + education_level = Column(Integer, nullable=False,comment='学历层次:1=大专,2=本科,3=硕士研究生,4=博士研究生') + # 逻辑删除标志,枚举,非空,默认值为 IsDeletedEnum.no('0'=未删除) + is_deleted = Column(Integer, default=IsDeletedEnum.no, nullable=False,comment='逻辑删除:0=未删除,1=已删除') + # 创建时间,非空,服务器默认值为当前时间(数据库端生成) + create_time = Column(DateTime, nullable=False, server_default=func.now(), comment='创建时间') + # 更新时间,非空,服务器默认值为当前时间,更新时自动刷新为当前时间 + update_time = Column(DateTime, nullable=False, server_default=func.now(), onupdate=func.now(), + comment='更新时间') + +# ======================== 地区 ========================== +# 地区维度表模型 +class Region_dimension(Base): + __tablename__ = 'region_dimension' # 表名 + # 表参数:utf8mb4 字符集 + __table_args__ = {'mysql_charset': 'utf8mb4', 'mysql_collate': 'utf8mb4_general_ci'} + + # 自增主键,BIGINT 类型 + id = Column(BIGINT, primary_key=True, autoincrement=True, comment='自增主键') + # 地区编码,唯一,非空,长度 12 + code = Column(VARCHAR(12), unique=True, nullable=False, comment='地区编码') + # 省 / 直辖市,非空 + province = Column(VARCHAR, nullable=False, comment='省 / 直辖市') + # 市,非空 + city = Column(VARCHAR, nullable=False, comment='市') + # 区 / 县,非空 + district = Column(VARCHAR, nullable=False, comment='区 / 县') + + +# 判断表是否存在,不存在则创建表 +# 检查 student_info 表是否存在 +if not inspect(db_engine).has_table('student_info'): + # 创建所有继承 Base 的表(此处会创建 student_info 和 region_dimension) + Base.metadata.create_all(db_engine) +# 检查 region_dimension 表是否存在 +if not inspect(db_engine).has_table('region_dimension'): + Base.metadata.create_all(db_engine) \ No newline at end of file diff --git a/schema/stu_info_region_schema.py b/schema/stu_info_region_schema.py new file mode 100644 index 0000000..caacf39 --- /dev/null +++ b/schema/stu_info_region_schema.py @@ -0,0 +1,84 @@ +# 从 pydantic 导入 BaseModel(模型基类)和 ConfigDict(模型配置) +from pydantic import BaseModel, ConfigDict +# 导入日期类型,用于 birthday、enrollment_date 等字段 +from datetime import date +# 导入 Optional,用于声明可选字段(可为 None) +from typing import Optional +# 从自定义枚举模块导入学生状态、性别、学历层次枚举 +from enums import StudentStatusEnum, GenderEnum, EducationLevelEnum + +# Optional 类型层面的声明,告诉 Pydantic:这个字段可以是 None + +# ======================== 学生 ========================== +# 学生信息请求体:用于新增/修改学生时的入参校验 +class stu_info_req(BaseModel): + student_id: str # 学号 + student_name: str # 学生姓名 + gender: GenderEnum # 性别(枚举:0=女,1=男) + id_card: str # 身份证号码 + birthday: date # 出生日期 + ethnicity: str # 民族 + region_id: str # 籍贯地区编码 + phone: str # 手机号码 + graduation_school: str # 毕业学校 + major: str # 就读专业 + class_id: str # 所在班级 + enrollment_date: date # 入学日期 + graduation_date: date # 毕业日期 + student_status: StudentStatusEnum # 学籍状态(枚举:0=在读,1=休学,2=退学,3=毕业,4=结业) + education_level: EducationLevelEnum # 学历层次(枚举:1=大专,2=本科,3=硕士,4=博士) + +# 学生信息响应体:用于查询学生时的出参序列化 +class stu_info_resp(BaseModel): + student_id: str # 学号 + student_name: str # 学生姓名 + gender: GenderEnum # 性别 + id_card: str # 身份证号码 + birthday: Optional[date] = None # 出生日期(可空) + ethnicity: Optional[str] = None # 民族(可空) + region_id: Optional[str] = None # 籍贯地区编码(可空) + phone: Optional[str] = None # 手机号码(可空) + graduation_school: Optional[str] = None # 毕业学校(可空) + major: str # 就读专业 + class_id: str # 所在班级 + enrollment_date: Optional[date] = None # 入学日期(可空) + graduation_date: Optional[date] = None # 毕业日期(可空) + student_status: StudentStatusEnum # 学籍状态 + education_level: EducationLevelEnum # 学历层次 + + # 配置:允许从 ORM 对象属性中读取数据(配合 from_attributes=True) + model_config = ConfigDict(from_attributes=True) + +# 学生信息增删改操作的统一响应体 +class stu_info_add_resp(BaseModel): + code: int # 状态码,200 表示成功 + message: Optional[str] = None # 提示信息(可空) + +# ======================== 地区 ========================== +# 地区维度表 添加请求体:用于新增地区时的入参校验 +class region_req(BaseModel): + code: str # 地区编码 + province: str # 省 / 直辖市 + city: str # 市 + district: str # 区 / 县 + +# 地区维度表 修改请求体:所有字段均可选,code 不在修改范围内 +class region_update_req(BaseModel): + province: Optional[str] = None # 省 / 直辖市(可选) + city: Optional[str] = None # 市(可选) + district: Optional[str] = None # 区 / 县(可选) + +# 地区维度 响应体:用于查询地区时的出参序列化 +class region_resp(BaseModel): + code: str # 地区编码 + province: str # 省 / 直辖市 + city: str # 市 + district: str # 区 / 县 + + # 配置:允许从 ORM 对象属性中读取数据 + model_config = ConfigDict(from_attributes=True) + +# 地区增删改操作的统一响应体 +class region_add_resp(BaseModel): + code: int # 状态码,200 表示成功 + message: Optional[str] = None # 提示信息(可空) \ No newline at end of file