From 4c813d657568042c3c17e2d281cce27f227343fd Mon Sep 17 00:00:00 2001 From: geeker Date: Wed, 23 Sep 2026 08:43:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=9E=B6=E6=9E=84=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E4=B8=8E=E4=B8=8B=E4=B8=80=E6=AD=A5=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- docs/00-overview.md | 77 +++++ docs/01-architecture-design.md | 251 ++++++++++++++++ docs/02-business-design.md | 198 +++++++++++++ docs/03-database-design.md | 436 ++++++++++++++++++++++++++++ docs/04-operations-deployment.md | 472 +++++++++++++++++++++++++++++++ docs/05-optimization-plan.md | 452 +++++++++++++++++++++++++++++ 7 files changed, 1887 insertions(+), 1 deletion(-) create mode 100644 docs/00-overview.md create mode 100644 docs/01-architecture-design.md create mode 100644 docs/02-business-design.md create mode 100644 docs/03-database-design.md create mode 100644 docs/04-operations-deployment.md create mode 100644 docs/05-optimization-plan.md diff --git a/.gitignore b/.gitignore index 18a6412..b6cfa23 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ __pycache__/ tests/ .git/ .github/ -*.md +#*.md !README.md .env .env.* diff --git a/docs/00-overview.md b/docs/00-overview.md new file mode 100644 index 0000000..a98abfc --- /dev/null +++ b/docs/00-overview.md @@ -0,0 +1,77 @@ +# 学生管理系统 — 项目总览 + +> 本文档汇总学生管理系统的架构、业务、数据库及运维部署设计,作为项目开发和维护的参考基准。 + +## 文档目录 + +| 序号 | 文档 | 说明 | +|------|------|------| +| 01 | [架构设计文档](./01-architecture-design.md) | 系统分层、模块划分、技术选型、部署架构 | +| 02 | [业务设计文档](./02-business-design.md) | 用户角色、业务流程、实体关系、统计规则 | +| 03 | [数据库设计文档](./03-database-design.md) | ER 图、DDL、索引策略、字典表、归档方案 | +| 04 | [运维部署文档](./04-operations-deployment.md) | 环境配置、Docker、Nginx、监控、备份、故障排查 | +| 05 | [优化文档](./05-optimization-plan.md) | 当前问题清单、优化方案、V2.0 规划、目录结构调整 | + +## 核心实体关系速查 + +``` +teachers ──1:N──► classes ──1:N──► students ──1:N──► scores + │ │ + │ 1:N (advisor) │ 1:1 + └────────────────────────────────────┴──► employment +``` + +## 技术栈 + +- **语言**:Python 3.13 +- **框架**:FastAPI 0.141+ +- **ORM**:SQLAlchemy 2.0+ +- **数据库**:MySQL 8.0(生产)/ SQLite(开发) +- **包管理**:uv +- **前端**:原生 HTML + JS(V2.0 规划迁移至 Vue3) + +## API 接口清单 + +| 模块 | 方法 | 路径 | 说明 | +|------|------|------|------| +| 教师 | GET | /api/teachers/ | 教师列表 | +| 教师 | GET | /api/teachers/{id} | 教师详情 | +| 教师 | POST | /api/teachers/ | 创建教师 | +| 教师 | PUT | /api/teachers/{id} | 更新教师 | +| 教师 | DELETE | /api/teachers/{id} | 软删除教师 | +| 班级 | GET | /api/classes/ | 班级列表 | +| 班级 | GET | /api/classes/{id} | 班级详情 | +| 班级 | POST | /api/classes/ | 创建班级 | +| 班级 | PUT | /api/classes/{id} | 更新班级 | +| 班级 | DELETE | /api/classes/{id} | 软删除班级 | +| 学生 | GET | /api/students/ | 学生列表 | +| 学生 | GET | /api/students/{id} | 学生详情 | +| 学生 | POST | /api/students/ | 创建学生 | +| 学生 | PUT | /api/students/{id} | 更新学生 | +| 学生 | DELETE | /api/students/{id} | 软删除学生 | +| 成绩 | GET | /api/scores/ | 成绩列表 | +| 成绩 | GET | /api/scores/{id} | 成绩详情 | +| 成绩 | POST | /api/scores/ | 录入成绩 | +| 成绩 | PUT | /api/scores/{id} | 更新成绩 | +| 成绩 | DELETE | /api/scores/{id} | 软删除成绩 | +| 就业 | GET | /api/employments/ | 就业列表 | +| 就业 | GET | /api/employments/{id} | 就业详情 | +| 就业 | POST | /api/employments/ | 创建就业记录 | +| 就业 | PUT | /api/employments/{id} | 更新就业记录 | +| 就业 | DELETE | /api/employments/{id} | 软删除就业记录 | +| 统计 | GET | /api/stats/classes | 班级人数统计 | +| 统计 | GET | /api/stats/scores | 成绩统计 | +| 统计 | GET | /api/stats/employment | 就业统计 | + +## V1.0 → V2.0 演进计划 + +| 优先级 | 功能项 | 影响模块 | +|--------|--------|----------| +| P0 | 用户认证(JWT/OAuth2) | main.py, routers/* | +| P0 | 字典表迁移(sex/education/coach_area) | models.py, seed.py | +| P1 | 部门管理模块 | 新增 routers/departments.py | +| P1 | 数据源配置(多环境 DB URL) | database.py | +| P2 | 前端重构(Vue3 + Element Plus) | static/ → frontend/ | +| P2 | 请求日志中间件 | main.py middleware | +| P3 | AI 知识库接入 | routers/ai.py(新模块) | +| P3 | 统计分析增强(趋势图、导出) | routers/stats.py | diff --git a/docs/01-architecture-design.md b/docs/01-architecture-design.md new file mode 100644 index 0000000..6b40da9 --- /dev/null +++ b/docs/01-architecture-design.md @@ -0,0 +1,251 @@ +# 学生管理系统 — 架构设计文档 + +## 1. 系统概述 + +学生管理系统(SMS)是一个面向教育培训机构的后台管理系统,提供学生信息管理、成绩管理、就业管理、班级管理及教师管理五大核心功能,以及统计分析大盘接口。 + +- **项目定位**:教培行业轻量级 SaaS 后台 +- **技术栈**:FastAPI + SQLAlchemy + PyMySQL + SQLite(开发)/ MySQL(生产) +- **目标用户**:教务管理员、班主任、顾问老师 + +--- + +## 2. 总体架构 + +### 2.1 架构分层 + +``` +┌─────────────────────────────────────────────────┐ +│ Client / Frontend │ +│ (Vue3 / 静态页面 / Postman) │ +└────────────────────────┬────────────────────────┘ + │ HTTP/REST +┌────────────────────────▼────────────────────────┐ +│ API Gateway / Load Balancer │ +│ (Nginx / Caddy,可选) │ +└────────────────────────┬────────────────────────┘ + │ +┌────────────────────────▼────────────────────────┐ +│ FastAPI 应用层 │ +│ ┌─────────────┐ ┌──────────────┐ ┌─────────┐ │ +│ │ Routers │ │ Middlewares │ │ Services│ │ +│ │ (REST API) │ │ (CORS/Auth) │ │ (待扩展) │ │ +│ └──────┬──────┘ └──────────────┘ └────┬────┘ │ +│ │ │ │ +│ ┌──────▼──────┐ ┌────────▼────┐ │ +│ │ Schemas │ │ Validators │ │ +│ │(Pydantic) │ │ (业务校验) │ │ +│ └──────┬──────┘ └─────────────┘ │ +│ │ │ +│ ┌──────▼──────┐ │ +│ │ Services │ ◄──── 业务逻辑(待拆分) │ +│ └──────┬──────┘ │ +└─────────┼────────────────────────────────────────┘ + │ SQLAlchemy ORM +┌─────────▼────────────────────────────────────────┐ +│ 数据访问层 │ +│ ┌──────────────┐ ┌──────────────────────────┐ │ +│ │ Repository │ │ BaseQuery / Mixins │ │ +│ │ (可选未来) │ │ (软删除、分页统一处理) │ │ +│ └──────────────┘ └──────────────────────────┘ │ +└─────────┬────────────────────────────────────────┘ + │ +┌─────────▼────────────────────────────────────────┐ +│ 数据库层 │ +│ ┌──────────────┐ ┌──────────────────────────┐ │ +│ │ MySQL │ │ SQLite(开发环境) │ │ +│ │ (生产) │ │ │ │ +│ └──────────────┘ └──────────────────────────┘ │ +└──────────────────────────────────────────────────┘ +``` + +### 2.2 架构演进路线 + +| 阶段 | 架构形态 | 说明 | +|------|----------|------| +| V1.0(当前)| 单体架构 | FastAPI 单体,SQLAlchemy ORM,sqlite/MySQL 双适配 | +| V2.0(规划)| 模块拆分 | 学生、成绩、就业等模块独立 Service 层,便于测试和复用 | +| V3.0(规划)| 微服务化 | 按业务域拆分独立服务,引入消息队列、服务注册发现 | + +--- + +## 3. 模块设计 + +### 3.1 模块划分 + +``` +student_manage_system/ +├── main.py # 应用入口,lifespan 管理 +├── database.py # DB 连接与 Session 工厂 +├── models.py # SQLAlchemy ORM 模型 +├── schemas.py # Pydantic 请求/响应 Schema +├── seed.py # 数据初始化脚本 +├── routers/ # API 路由层 +│ ├── __init__.py +│ ├── teachers.py # 教师管理模块 +│ ├── classes.py # 班级管理模块 +│ ├── students.py # 学生管理模块 +│ ├── scores.py # 成绩管理模块 +│ ├── employment.py # 就业管理模块 +│ └── stats.py # 统计分析模块 +├── static/ # 静态前端 +│ └── index.html +├── docs/ # 项目文档 +├── pyproject.toml # 项目依赖配置 +└── uv.lock # 依赖锁定文件 +``` + +### 3.2 模块职责矩阵 + +| 模块 | 路径 | 核心职责 | HTTP 前缀 | +|------|------|----------|-----------| +| 教师管理 | routers/teachers.py | 教师 CRUD | /api/teachers | +| 班级管理 | routers/classes.py | 班级 CRUD,关联教师分配 | /api/classes | +| 学生管理 | routers/students.py | 学生 CRUD,关联班级/顾问 | /api/students | +| 成绩管理 | routers/scores.py | 成绩录入、修改、软删除 | /api/scores | +| 就业管理 | routers/employment.py | 就业信息 CRUD | /api/employments | +| 统计分析 | routers/stats.py | 班级/成绩/就业维度统计 | /api/stats | + +--- + +## 4. 技术选型说明 + +### 4.1 后端框架:FastAPI +- 原生异步支持,适合 IO 密集型场景 +- 自动 OpenAPI 文档生成(`/docs`、`/redoc`) +- Pydantic 内置数据校验,减少样板代码 +- 依赖注入(`Depends`)支持 Session 管理和鉴权扩展 + +### 4.2 ORM:SQLAlchemy 2.0 +- 声明式模型,支持 `Mapped` / `Column` 新风格 +- 与 PyMySQL 驱动无缝集成 +- 软删除模式通过基类 `BaseModel` 统一实现 + +### 4.3 数据库驱动 +| 环境 | 驱动 | 连接方式 | +|------|------|----------| +| 开发 | pymysql(SQLite 直连)| 文件数据库,无需部署 | +| 生产 | pymysql | MySQL 8.0+ / PostgreSQL 14+ | + +### 4.4 包管理:uv +- 比 pip 快 10-100x 的 Python 包管理器 +- 统一的锁文件管理(`uv.lock`) +- `uv sync` 确保多环境一致 + +--- + +## 5. 接口设计规范 + +### 5.1 RESTful 风格 +- 资源使用名词复数:`/api/students` +- 统一路径前缀:`/api/` +- 软删除代替硬删除,返回语义一致的响应码 + +### 5.2 统一响应格式 + +| 操作 | HTTP 方法 | 成功状态码 | 失败典型状态码 | +|------|-----------|------------|----------------| +| 列表查询 | GET | 200 | 400(参数错误) | +| 详情查询 | GET /{id} | 200 | 404(不存在) | +| 创建 | POST | 201 | 400(重复/校验失败) | +| 更新 | PUT /{id} | 200 | 404 / 400 | +| 删除 | DELETE /{id} | 200 | 404 | + +### 5.3 分页规范 +所有列表接口统一支持: +- `skip`(偏移量,默认 0) +- `limit`(页大小,默认 100) + +--- + +## 6. 中间件设计 + +### 6.1 当前中间件 +```python +# CORS 中间件(开发环境全放行,生产需收紧) +CORSMiddleware( + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) +``` + +### 6.2 待补充中间件(V2.0 规划) +- **认证鉴权中间件**:基于 OAuth2 + JWT 的请求拦截 +- **请求日志中间件**:统一记录请求耗时、状态码、IP +- **限流中间件**:防止单接口高频调用 + +--- + +## 7. 部署架构 + +### 7.1 开发环境 +``` +本地开发机 +├── uv run uvicorn main:app --reload +├── SQLite(本地文件 sms.db) +└── 浏览器访问 http://localhost:8000 +``` + +### 7.2 生产环境(推荐) + +``` + ┌─────────────┐ + │ Nginx │ ← 反向代理 + HTTPS 终止 + │ (port 443) │ + └──────┬──────┘ + │ + ┌──────────────┼──────────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌──────────┐ + │ FastAPI │ │ FastAPI │ │ FastAPI │ ← 水平扩展(多实例) + │ :8000 │ │ :8001 │ │ :8002 │ + └────┬─────┘ └────┬─────┘ └────┬─────┘ + │ │ │ + └─────────────┼─────────────┘ + ▼ + ┌────────────────┐ + │ MySQL 集群 │ ← 主从复制 / 云数据库 + │ (port 3306) │ + └────────────────┘ +``` + +### 7.3 容器化部署(Docker) +```dockerfile +FROM python:3.13-slim +WORKDIR /app +COPY pyproject.toml uv.lock ./ +RUN uv sync --frozen --no-dev +COPY . . +EXPOSE 8000 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"] +``` + +--- + +## 8. 安全设计 + +| 层次 | 措施 | 当前状态 | +|------|------|----------| +| 传输层 | HTTPS(生产) | 规划中 | +| 应用层 | Pydantic 数据校验 | ✅ 已实现 | +| 应用层 | 软删除防止数据误删 | ✅ 已实现 | +| 认证层 | JWT Token | 规划中 | +| 授权层 | RBAC 角色权限 | 规划中 | +| 注入防护 | SQLAlchemy 参数化查询 | ✅ 已实现 | + +--- + +## 9. 可扩展性设计 + +### 9.1 当前已预留扩展点 +- `lifespan` 钩子:启动时自动建表和种子数据,后续可扩展为迁移工具(Alembic) +- 路由模块化:各业务模块独立文件,便于拆分微服务 +- `BaseModel` 基类:所有模型共享审计字段,后续可加分布式 ID + +### 9.2 未来演进方向 +1. **多租户支持**:在 `BaseModel` 中加入 `tenant_id` 字段 +2. **缓存层**:Redis 缓存统计结果,减轻数据库压力 +3. **异步任务**:Celery / APScheduler 处理离线统计和报告生成 +4. **事件驱动**:学生/成绩变更时发布事件,供下游系统消费 diff --git a/docs/02-business-design.md b/docs/02-business-design.md new file mode 100644 index 0000000..da30e26 --- /dev/null +++ b/docs/02-business-design.md @@ -0,0 +1,198 @@ +# 学生管理系统 — 业务设计文档 + +## 1. 业务背景 + +本系统服务于教育培训机构的日常教务运营,解决以下核心业务问题: +- **学生信息管理**:统一管理学员档案,支持按班级、顾问老师查询 +- **教学过程跟踪**:记录各阶段考核成绩,分析学习效果 +- **就业数据沉淀**:追踪毕业生去向,统计就业率和薪资水平 +- **班级师资配置**:管理班主任、授课老师、助教老师的协作关系 + +--- + +## 2. 用户角色 + +| 角色 | 说明 | 主要操作 | +|------|------|----------| +| 教务管理员 | 系统最高权限,负责全局数据维护 | 全部 CRUD、统计查看 | +| 班主任 | 负责班级管理 | 查看/编辑本班学生、班级信息 | +| 授课老师 | 负责成绩录入 | 录入/修改学生成绩 | +| 顾问老师 | 负责学生职业规划 | 查看所带学生信息、就业状态 | +| 就业专员 | 负责就业数据维护 | 录入/更新就业记录 | + +> 当前阶段为内部工具,暂未实现角色权限控制;V2.0 规划接入 OAuth2 + RBAC。 + +--- + +## 3. 业务流程 + +### 3.1 学生入班流程 + +``` +[创建教师] → [创建班级并分配师资] → [录入学生信息并绑定班级] + │ │ │ + └──── 教师编号唯一 ──┘ └── 学号唯一,班级必填 +``` + +**规则**: +- 新建学生时,`class_id` 必须指向已存在的未删除班级 +- `advisor_id`(顾问老师)可选,必须指向有效教师 +- 学生编号(`num`)全局唯一 + +### 3.2 成绩录入流程 + +``` +[选择学生] → [填写考核序次(num)] → [录入分数] + │ + └── 同一学生同一考核序次不可重复录入(业务约束) +``` + +**规则**: +- 成绩 `score` 保留两位小数(DECIMAL(10,2)) +- 考核序次(如"第一次考核"、"期中")由业务方定义,系统不做枚举约束 +- 支持按学生 ID 或考核序次模糊查询 + +### 3.3 就业登记流程 + +``` +[开放就业通道] → [登记就业公司信息] → [下发 Offer] → [记录薪资] + │ │ │ + employment_open employment_company offer_received + _time _time time +``` + +**规则**: +- 一个学生只能有一条有效就业记录(`sid` 唯一约束) +- `employment_open_time` 和 `offer_recived_time` 可为空(流程进行中) +- 平均签约周期 = `offer_recived_time - employment_open_time`(天数) + +### 3.4 统计查询流程 + +``` +[请求统计数据] → [按班级/考核序次过滤] → [聚合计算] → [返回统计结果] +``` + +| 统计类型 | 维度 | 指标 | +|----------|------|------| +| 班级人数统计 | 按班级 | 总人数、男生数、女生数 | +| 成绩统计 | 按考核序次+班级 | 平均分、最高分、最低分、参考人数 | +| 就业统计 | 按班级 | 已就业人数、就业率、平均薪资、平均签约周期 | + +--- + +## 4. 核心业务实体关系 + +``` +┌──────────┐ 1:N ┌──────────┐ 1:N ┌──────────┐ +│ Teacher │◄───────────►│ Class │◄───────────►│ Student │ +│ 教师 │ 班主任/授课 │ 班级 │ 所属班级 │ 学生 │ +│ │ 助教老师 │ │ │ │ +└────┬─────┘ └────┬─────┘ └────┬─────┘ + │ │ │ + │ 1:N │ 1:1 │ 1:N + ▼ ▼ ▼ +┌──────────┐ ┌──────────┐ ┌──────────┐ +│ Score │ │Employment│◄──────────►│ Student │ +│ 成绩 │ │ 就业 │ 一对一 │ │ +└──────────┘ └──────────┘ └──────────┘ +``` + +### 关联说明 +- **Teacher ↔ Class**:一对多(一个老师可担任多个班级的班主任/授课老师/助教) +- **Class ↔ Student**:一对多(一个班级有多个学生) +- **Student ↔ Score**:一对多(一个学生有多次考核成绩) +- **Student ↔ Employment**:一对一(一个学生只有一条有效就业记录) +- **Teacher ↔ Student**:多对一(一个顾问老师带多个学生) + +--- + +## 5. 业务规则与约束 + +### 5.1 唯一性约束 +| 字段 | 表 | 规则 | +|------|-----|------| +| `num` | teachers | 教师编号全局唯一 | +| `num` | classes | 班级编号全局唯一 | +| `num` | students | 学生编号全局唯一 | +| (sid, num) | scores | 同一学生同一考核序次只允许一条记录 | +| `sid` | employment | 一名学生只允许一条就业记录 | + +### 5.2 外键约束(业务层校验) +| 字段 | 引用表 | 级联策略 | +|------|--------|----------| +| `students.class_id` | `classes.id` | 删除班级时限制(restrict),更新级联 | +| `students.advisor_id` | `teachers.id` | 删除教师时置空,更新级联 | +| `classes.head_teacher_id` | `teachers.id` | 同左 | +| `scores.sid` | `students.id` | 删除学生时级联删除成绩 | +| `employment.sid` | `students.id` | 删除学生时级联删除就业记录 | + +### 5.3 软删除规则 +- 所有表均支持软删除(`is_deleted = true` + `delete_time` 记录时间) +- 所有查询默认过滤 `is_deleted = false` +- 软删除不影响关联数据(成绩/就业随学生级联删除) + +--- + +## 6. 统计业务逻辑详细说明 + +### 6.1 班级人数统计(`GET /api/stats/classes`) +``` +SELECT class_id, class_name, COUNT(*) as total, + SUM(CASE WHEN sex=1 THEN 1 ELSE 0 END) as male_count, + SUM(CASE WHEN sex=2 THEN 1 ELSE 0 END) as female_count +FROM students s JOIN classes c ON s.class_id = c.id +WHERE s.is_deleted=0 AND c.is_deleted=0 +GROUP BY s.class_id, c.name +``` + +### 6.2 成绩统计(`GET /api/stats/scores?class_id=&score_num=`) +``` +SELECT score_num, class_id, AVG(score), MAX(score), MIN(score), COUNT(*) +FROM scores sc JOIN students s ON sc.sid = s.id +WHERE s.is_deleted=0 AND sc.is_deleted=0 +GROUP BY score_num, class_id +[可选过滤 class_id / score_num] +``` + +### 6.3 就业统计(`GET /api/stats/employment?class_id=`) +``` +SELECT e.class_id, c.name, + COUNT(e.id) as employed_count, + COUNT(s.id) as total_count, + AVG(e.employment_salary) as avg_salary +FROM employment e +JOIN classes c ON e.class_id = c.id +JOIN students s ON e.sid = s.id +WHERE e.is_deleted=0 AND s.is_deleted=0 AND c.is_deleted=0 +GROUP BY e.class_id, c.name +[可选过滤 class_id] + +-- 额外计算:平均签约周期(天数) +AVG(JULIANDAY(offer_recived_time) - JULIANDAY(employment_open_time)) +WHERE employment_open_time IS NOT NULL AND offer_recived_time IS NOT NULL +``` + +--- + +## 7. 数据字典(待 V2.0 实现) + +以下字段当前以字符串或整数存储,建议 V2.0 升级为字典表管理: + +| 字段 | 当前存储 | 建议字典类型 | 候选值 | +|------|----------|-------------|--------| +| `sex` | TINYINT (0/1/2) | dict_type=sex | 0:未知, 1:男, 2:女 | +| `education` | VARCHAR | dict_type=education | bachelor:本科, master:硕士, phd:博士 | +| `coach_area` | VARCHAR | dict_type=coach_area | app:应用, project:项目, algorithm:算法 | +| `college` | VARCHAR | dict_type=college | 院校代码 → 名称映射 | +| `specialty` | VARCHAR | dict_type=specialty | 专业代码 → 名称映射 | + +--- + +## 8. 分期业务规划 + +| 阶段 | 业务目标 | 功能范围 | +|------|----------|----------| +| V1.0(当前)| 基础数据管理 | 五模块 CRUD + 三类统计 | +| V2.0 | 数据治理与权限 | 字典表、用户认证、部门/顾问模块、数据源配置 | +| V3.0 | 智能分析 | AI 知识库接入、自然语言查询、预测分析 | +| V4.0 | 多租户 SaaS | 租户隔离、多机构支持、API 开放平台 | diff --git a/docs/03-database-design.md b/docs/03-database-design.md new file mode 100644 index 0000000..60463fa --- /dev/null +++ b/docs/03-database-design.md @@ -0,0 +1,436 @@ +# 学生管理系统 — 数据库设计文档 + +## 1. 数据库概述 + +| 属性 | 说明 | +|------|------| +| 数据库名 | `sms`(学生管理系统 Student Manage System) | +| 存储引擎 | InnoDB | +| 字符集 | utf8mb4 | +| 默认排序规则 | utf8mb4_unicode_ci | +| 通用字段方案 | `id`、`create_time`、`update_time`、`is_deleted`、`delete_time` | + +--- + +## 2. ER 关系图 + +``` +┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ +│ teachers │ │ classes │ │ students │ +├──────────────────┤ ├──────────────────┤ ├──────────────────┤ +│ id (PK) │ │ id (PK) │ │ id (PK) │ +│ num (UK) │ │ num (UK) │ │ num (UK) │ +│ name │ │ name │ │ name │ +│ age │ │ class_start_time │ │ age │ +│ sex │ │ │ │ sex │ +│ home_place │ │ head_teacher_id │───┐ │ home_place │ +│ college │ │ coach_teacher_id │ │ │ college │ +│ specialty │ │ tutor_teacher_id │ │ │ specialty │ +│ enrollment_time │ └──────┬───────────┘ │ │ enrollment_time │ +│ graduate_time │ │ │ │ graduate_time │ +│ education │ 1:N ▼ N:1 │ │ education │ +│ work_experience │ ┌──────────────────┐ │ │ │ +│ coach_area │ │ students │◄──┘ │ create_time │ +│ │ │ (class_id FK) │ │ update_time │ +│ create_time │ └──────────────────┘ │ is_deleted │ +│ update_time │ │ │ delete_time │ +│ is_deleted │ │ 1:N └──────────────────┘ +│ delete_time │ ▼ +└──────────────────┘ ┌──────────────────┐ ┌──────────────────┐ + │ scores │ │ employment │ + ├──────────────────┤ ├──────────────────┤ + │ id (PK) │ │ id (PK) │ + │ sid (FK→students)│ │ sid (FK→students)│ + │ num │ │ class_id (FK) │ + │ score │ │ employment_open │ + │ │ │ _time │ + │ create_time │ │ offer_recived │ + │ update_time │ │ _time │ + │ is_deleted │ │ employment_comp │ + │ delete_time │ │ any │ + └──────────────────┘ │ employment_salar │ + │ y │ + │ │ + │ create_time │ + │ update_time │ + │ is_deleted │ + │ delete_time │ + └──────────────────┘ +``` + +--- + +## 3. 表结构详述 + +### 3.1 teachers — 教师表 + +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | INT | PK, AUTO_INCREMENT | 主键 | +| num | VARCHAR(30) | UNIQUE, NOT NULL, INDEX | 教师编号 | +| name | VARCHAR(30) | NOT NULL | 姓名 | +| age | INT | DEFAULT 18 | 年龄 | +| sex | INT | DEFAULT 0 | 性别:0未知 1男 2女 | +| home_place | VARCHAR(50) | NULL | 家乡 | +| college | VARCHAR(50) | NULL | 毕业院校 | +| specialty | VARCHAR(50) | NULL | 专业 | +| enrollment_time | DATETIME | NULL | 入学时间 | +| graduate_time | DATETIME | NULL | 毕业时间 | +| education | VARCHAR(50) | NULL | 学历 | +| work_experience | TEXT | NULL | 工作经验 | +| coach_area | VARCHAR(50) | NULL | 教学方向:应用/项目/算法 | +| create_time | DATETIME | NOT NULL | 创建时间 | +| update_time | DATETIME | NOT NULL | 更新时间 | +| is_deleted | BOOLEAN | DEFAULT FALSE | 软删除标志 | +| delete_time | DATETIME | NULL | 删除时间 | + +**索引:** +```sql +PRIMARY KEY (id), +UNIQUE KEY uk_tnum (num), +KEY idx_num (num) +``` + +**外键:** 无(teachers 作为被引用方) + +--- + +### 3.2 classes — 班级表 + +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | INT | PK, AUTO_INCREMENT | 主键 | +| num | VARCHAR(30) | UNIQUE, NOT NULL, INDEX | 班级编号 | +| name | VARCHAR(30) | NOT NULL | 班级名称 | +| class_start_time | DATETIME | NULL | 开课时间 | +| head_teacher_id | INT | NULL, FK→teachers.id | 班主任 ID | +| coach_teacher_id | INT | NULL, FK→teachers.id | 授课老师 ID | +| tutor_teacher_id | INT | NULL, FK→teachers.id | 助教老师 ID | +| create_time | DATETIME | NOT NULL | 创建时间 | +| update_time | DATETIME | NOT NULL | 更新时间 | +| is_deleted | BOOLEAN | DEFAULT FALSE | 软删除标志 | +| delete_time | DATETIME | NULL | 删除时间 | + +**索引:** +```sql +PRIMARY KEY (id), +UNIQUE KEY uk_class_num (num), +KEY idx_head_teacher (head_teacher_id), +KEY idx_coach_teacher (coach_teacher_id), +KEY idx_tutor_teacher (tutor_teacher_id) +``` + +**外键:** +```sql +CONSTRAINT fk_classes_head_teacher FOREIGN KEY (head_teacher_id) REFERENCES teachers(id) ON DELETE SET NULL ON UPDATE CASCADE, +CONSTRAINT fk_classes_coach_teacher FOREIGN KEY (coach_teacher_id) REFERENCES teachers(id) ON DELETE SET NULL ON UPDATE CASCADE, +CONSTRAINT fk_classes_tutor_teacher FOREIGN KEY (tutor_teacher_id) REFERENCES teachers(id) ON DELETE SET NULL ON UPDATE CASCADE +``` + +--- + +### 3.3 students — 学生表 + +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | INT | PK, AUTO_INCREMENT | 主键 | +| num | VARCHAR(30) | UNIQUE, NOT NULL, INDEX | 学生编号 | +| name | VARCHAR(30) | NOT NULL | 姓名 | +| age | INT | DEFAULT 18 | 年龄 | +| sex | INT | DEFAULT 0 | 性别:0未知 1男 2女 | +| home_place | VARCHAR(50) | NULL | 家乡 | +| college | VARCHAR(50) | NULL | 毕业院校 | +| specialty | VARCHAR(50) | NULL | 专业 | +| enrollment_time | DATETIME | NULL | 入学时间 | +| graduate_time | DATETIME | NULL | 毕业时间 | +| education | VARCHAR(50) | NULL | 学历 | +| class_id | INT | NOT NULL, DEFAULT 0, INDEX | 所属班级 ID | +| advisor_id | INT | NULL, FK→teachers.id | 顾问老师 ID | +| create_time | DATETIME | NOT NULL | 创建时间 | +| update_time | DATETIME | NOT NULL | 更新时间 | +| is_deleted | BOOLEAN | DEFAULT FALSE | 软删除标志 | +| delete_time | DATETIME | NULL | 删除时间 | + +**索引:** +```sql +PRIMARY KEY (id), +UNIQUE KEY uk_snum (num), +KEY idx_class_id (class_id), +KEY idx_advisor_id (advisor_id), +KEY idx_class_deleted (class_id, is_deleted) -- 复合索引,优化班级学生列表查询 +``` + +**外键:** +```sql +CONSTRAINT fk_students_class FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE RESTRICT ON UPDATE CASCADE, +CONSTRAINT fk_students_advisor FOREIGN KEY (advisor_id) REFERENCES teachers(id) ON DELETE SET NULL ON UPDATE CASCADE +``` + +> **注意**:`class_id` 使用 `ON DELETE RESTRICT`,防止误删有学生的班级。 + +--- + +### 3.4 scores — 成绩表 + +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | INT | PK, AUTO_INCREMENT | 主键 | +| sid | INT | NOT NULL, FK→students.id, INDEX | 学生 ID | +| num | VARCHAR(30) | NOT NULL | 考核序次(如"第一次考核") | +| score | DECIMAL(10,2) | NOT NULL | 分数 | +| create_time | DATETIME | NOT NULL | 创建时间 | +| update_time | DATETIME | NOT NULL | 更新时间 | +| is_deleted | BOOLEAN | DEFAULT FALSE | 软删除标志 | +| delete_time | DATETIME | NULL | 删除时间 | + +**索引:** +```sql +PRIMARY KEY (id), +UNIQUE KEY uk_sid_score (sid, num), -- 防止同一学生同一考核重复录入 +KEY idx_sid (sid) +``` + +**外键:** +```sql +CONSTRAINT fk_scores_student FOREIGN KEY (sid) REFERENCES students(id) ON DELETE CASCADE ON UPDATE CASCADE +``` + +> 删除学生时,其所有成绩记录自动级联删除。 + +--- + +### 3.5 employment — 就业表 + +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | INT | PK, AUTO_INCREMENT | 主键 | +| sid | INT | UNIQUE, FK→students.id | 学生 ID(一对一) | +| class_id | INT | NOT NULL, INDEX | 所属班级 ID | +| employment_open_time | DATETIME | NULL | 就业开放时间 | +| offer_recived_time | DATETIME | NULL | Offer 下发时间 | +| employment_company | VARCHAR(50) | NULL | 就业公司 | +| employment_salary | DECIMAL(20,5) | NULL | 就业薪资(月薪) | +| create_time | DATETIME | NOT NULL | 创建时间 | +| update_time | DATETIME | NOT NULL | 更新时间 | +| is_deleted | BOOLEAN | DEFAULT FALSE | 软删除标志 | +| delete_time | DATETIME | NULL | 删除时间 | + +**索引:** +```sql +PRIMARY KEY (id), +UNIQUE KEY uk_sid (sid), -- 一名学生只允许一条就业记录 +KEY idx_class_id (class_id), +KEY idx_class_deleted (class_id, is_deleted) -- 复合索引,优化班级就业统计 +``` + +**外键:** +```sql +CONSTRAINT fk_employment_student FOREIGN KEY (sid) REFERENCES students(id) ON DELETE CASCADE ON UPDATE CASCADE, +CONSTRAINT fk_employment_class FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE RESTRICT ON UPDATE CASCADE +``` + +--- + +## 4. 完整 DDL(可直接执行) + +```sql +CREATE DATABASE IF NOT EXISTS sms + CHARACTER SET utf8mb4 + COLLATE utf8mb4_unicode_ci; + +USE sms; + +-- ==================== teachers ==================== +CREATE TABLE IF NOT EXISTS teachers ( + id INT AUTO_INCREMENT PRIMARY KEY, + num VARCHAR(30) NOT NULL COMMENT '教师编号', + name VARCHAR(30) NOT NULL DEFAULT '' COMMENT '姓名', + age INT NOT NULL DEFAULT 18 COMMENT '年龄', + sex INT NOT NULL DEFAULT 0 COMMENT '性别:0未知 1男 2女', + home_place VARCHAR(50) NULL COMMENT '家乡', + college VARCHAR(50) NULL COMMENT '毕业院校', + specialty VARCHAR(50) NULL COMMENT '专业', + enrollment_time DATETIME NULL COMMENT '入学时间', + graduate_time DATETIME NULL COMMENT '毕业时间', + education VARCHAR(50) NULL COMMENT '学历', + work_experience TEXT NULL COMMENT '工作经验', + coach_area VARCHAR(50) NULL COMMENT '教学方向:应用/项目/算法', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + is_deleted TINYINT(1) NOT NULL DEFAULT 0, + delete_time DATETIME NULL, + UNIQUE KEY uk_tnum (num), + KEY idx_num (num) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='教师表'; + +-- ==================== classes ==================== +CREATE TABLE IF NOT EXISTS classes ( + id INT AUTO_INCREMENT PRIMARY KEY, + num VARCHAR(30) NOT NULL COMMENT '班级编号', + name VARCHAR(30) NOT NULL COMMENT '班级名称', + class_start_time DATETIME NULL COMMENT '开课时间', + head_teacher_id INT NULL COMMENT '班主任ID', + coach_teacher_id INT NULL COMMENT '授课老师ID', + tutor_teacher_id INT NULL COMMENT '助教老师ID', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + is_deleted TINYINT(1) NOT NULL DEFAULT 0, + delete_time DATETIME NULL, + UNIQUE KEY uk_class_num (num), + KEY idx_head_teacher (head_teacher_id), + KEY idx_coach_teacher (coach_teacher_id), + KEY idx_tutor_teacher (tutor_teacher_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='班级表'; + +ALTER TABLE classes + ADD CONSTRAINT fk_classes_head_teacher FOREIGN KEY (head_teacher_id) REFERENCES teachers(id) ON DELETE SET NULL ON UPDATE CASCADE, + ADD CONSTRAINT fk_classes_coach_teacher FOREIGN KEY (coach_teacher_id) REFERENCES teachers(id) ON DELETE SET NULL ON UPDATE CASCADE, + ADD CONSTRAINT fk_classes_tutor_teacher FOREIGN KEY (tutor_teacher_id) REFERENCES teachers(id) ON DELETE SET NULL ON UPDATE CASCADE; + +-- ==================== students ==================== +CREATE TABLE IF NOT EXISTS students ( + id INT AUTO_INCREMENT PRIMARY KEY, + num VARCHAR(30) NOT NULL COMMENT '学生编号', + name VARCHAR(30) NOT NULL DEFAULT '' COMMENT '姓名', + age INT NOT NULL DEFAULT 18 COMMENT '年龄', + sex INT NOT NULL DEFAULT 0 COMMENT '性别:0未知 1男 2女', + home_place VARCHAR(50) NULL COMMENT '家乡', + college VARCHAR(50) NULL COMMENT '毕业院校', + specialty VARCHAR(50) NULL COMMENT '专业', + enrollment_time DATETIME NULL COMMENT '入学时间', + graduate_time DATETIME NULL COMMENT '毕业时间', + education VARCHAR(50) NULL COMMENT '学历', + class_id INT NOT NULL DEFAULT 0 COMMENT '所属班级ID', + advisor_id INT NULL COMMENT '顾问老师ID', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + is_deleted TINYINT(1) NOT NULL DEFAULT 0, + delete_time DATETIME NULL, + UNIQUE KEY uk_snum (num), + KEY idx_class_id (class_id), + KEY idx_advisor_id (advisor_id), + KEY idx_class_deleted (class_id, is_deleted) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生表'; + +ALTER TABLE students + ADD CONSTRAINT fk_students_class FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE RESTRICT ON UPDATE CASCADE, + ADD CONSTRAINT fk_students_advisor FOREIGN KEY (advisor_id) REFERENCES teachers(id) ON DELETE SET NULL ON UPDATE CASCADE; + +-- ==================== scores ==================== +CREATE TABLE IF NOT EXISTS scores ( + id INT AUTO_INCREMENT PRIMARY KEY, + sid INT NOT NULL COMMENT '学生ID', + num VARCHAR(30) NOT NULL COMMENT '考核序次', + score DECIMAL(10,2) NOT NULL COMMENT '分数', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + is_deleted TINYINT(1) NOT NULL DEFAULT 0, + delete_time DATETIME NULL, + UNIQUE KEY uk_sid_score (sid, num), + KEY idx_sid (sid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成绩表'; + +ALTER TABLE scores + ADD CONSTRAINT fk_scores_student FOREIGN KEY (sid) REFERENCES students(id) ON DELETE CASCADE ON UPDATE CASCADE; + +-- ==================== employment ==================== +CREATE TABLE IF NOT EXISTS employment ( + id INT AUTO_INCREMENT PRIMARY KEY, + sid INT NOT NULL COMMENT '学生ID', + class_id INT NOT NULL COMMENT '班级ID', + employment_open_time DATETIME NULL COMMENT '就业开放时间', + offer_recived_time DATETIME NULL COMMENT 'Offer下发时间', + employment_company VARCHAR(50) NULL COMMENT '就业公司', + employment_salary DECIMAL(20,5) NULL COMMENT '就业薪资(月薪)', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + is_deleted TINYINT(1) NOT NULL DEFAULT 0, + delete_time DATETIME NULL, + UNIQUE KEY uk_sid (sid), + KEY idx_class_id (class_id), + KEY idx_class_deleted (class_id, is_deleted) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='就业表'; + +ALTER TABLE employment + ADD CONSTRAINT fk_employment_student FOREIGN KEY (sid) REFERENCES students(id) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT fk_employment_class FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE RESTRICT ON UPDATE CASCADE; +``` + +--- + +## 5. 索引设计说明 + +| 表 | 索引名 | 类型 | 字段 | 优化场景 | +|----|--------|------|------|----------| +| students | uk_snum | 唯一 | num | 按学号精确查询 | +| students | idx_class_id | 普通 | class_id | 按班级查学生列表 | +| students | idx_advisor_id | 普通 | advisor_id | 按顾问查学生 | +| students | idx_class_deleted | 联合 | (class_id, is_deleted) | 查某班未删除学生(覆盖常见过滤) | +| classes | uk_class_num | 唯一 | num | 按班级编号精确查询 | +| classes | idx_head_teacher | 普通 | head_teacher_id | 按班主任查班级 | +| scores | uk_sid_score | 唯一 | (sid, num) | 防止重复录入,加速组合查询 | +| scores | idx_sid | 普通 | sid | 按学生查成绩 | +| employment | uk_sid | 唯一 | sid | 保证一人一条就业记录 | +| employment | idx_class_id | 普通 | class_id | 按班级统计就业 | +| employment | idx_class_deleted | 联合 | (class_id, is_deleted) | 班级就业统计过滤已删除记录 | + +--- + +## 6. 数据字典表(V2.0 规划) + +```sql +CREATE TABLE IF NOT EXISTS dict_items ( + id INT AUTO_INCREMENT PRIMARY KEY, + dict_type VARCHAR(50) NOT NULL COMMENT '字典类型:sex, education, college, specialty, coach_area', + dict_code VARCHAR(50) NOT NULL COMMENT '字典编码,业务表存储的值', + dict_label VARCHAR(100) NOT NULL COMMENT '显示名称', + sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重', + is_enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + is_deleted TINYINT(1) NOT NULL DEFAULT 0, + delete_time DATETIME NULL, + UNIQUE KEY uk_type_code (dict_type, dict_code), + KEY idx_type (dict_type) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通用字典表'; + +-- 初始数据 +INSERT INTO dict_items (dict_type, dict_code, dict_label, sort_order) VALUES +('sex', '0', '未知', 1), +('sex', '1', '男', 2), +('sex', '2', '女', 3), +('education','bachelor', '本科', 1), +('education','master', '硕士', 2), +('education','phd', '博士', 3), +('coach_area','app', '应用', 1), +('coach_area','project', '项目', 2), +('coach_area','algorithm','算法', 3); +``` + +**引用方式(应用层 JOIN):** +```sql +-- 查询学生时关联字典获取可读名称 +SELECT s.*, + d_sex.dict_label AS sex_label, + d_edu.dict_label AS education_label +FROM students s +LEFT JOIN dict_items d_sex ON d_sex.dict_type='sex' AND d_sex.dict_code=CAST(s.sex AS CHAR) +LEFT JOIN dict_items d_edu ON d_edu.dict_type='education' AND d_edu.dict_code=s.education +WHERE s.is_deleted=0; +``` + +--- + +## 7. 历史数据归档策略(V3.0 规划) + +当数据量达到千万级时,考虑以下归档方案: + +| 策略 | 适用场景 | 说明 | +|------|----------|------| +| 分区表(PARTITION BY RANGE) | 按年份归档 | 对 `create_time` 做范围分区,老分区可迁移至冷存储 | +| 归档表(_history 后缀) | 毕业生数据 | 将已毕业学生的成绩/就业记录移入 `students_history` 等归档表 | +| 分库分表 | 超大规模 | 按 `class_id` 哈希分片,需引入 ShardingSphere 或应用层分片 | + +**归档判断条件:** +- 学生 `graduate_time` 距今超过 2 年 +- 或主动标记为"已归档"状态 diff --git a/docs/04-operations-deployment.md b/docs/04-operations-deployment.md new file mode 100644 index 0000000..0cb1ed6 --- /dev/null +++ b/docs/04-operations-deployment.md @@ -0,0 +1,472 @@ +# 学生管理系统 — 运维部署文档 + +## 1. 环境说明 + +| 环境 | 用途 | 访问地址 | 数据库 | +|------|------|----------|--------| +| 本地开发 | 功能开发、调试 | http://localhost:8000 | SQLite(文件) | +| 测试环境 | 集成测试、QA 验证 | http://sms-test.internal:8000 | MySQL 5.7+ | +| 生产环境 | 线上服务 | https://sms.example.com | MySQL 8.0 主从集群 | + +--- + +## 2. 本地开发部署 + +### 2.1 前置依赖 + +```bash +# Python 3.13+ +# uv 包管理器 +pip install uv +``` + +### 2.2 一键启动 + +```bash +# 进入项目目录 +cd student_manage_system + +# 同步依赖(首次运行) +uv sync + +# 启动开发服务器(热重载) +uv run uvicorn main:app --reload --host 0.0.0.0 --port 8000 +``` + +### 2.3 验证 + +```bash +# 访问 API 文档 +curl http://localhost:8000/docs + +# 健康检查 +curl http://localhost:8000/api/teachers/ +``` + +### 2.4 初始化数据 + +应用启动时 `lifespan` 自动调用 `seed.init_db()` 建表,`seed.seed_data()` 写入测试数据。 +如需重新初始化: + +```bash +# 删除本地数据库文件后重启即可 +rm -f sms.db +uv run uvicorn main:app --reload +``` + +--- + +## 3. Docker 部署 + +### 3.1 Dockerfile + +```dockerfile +# 多阶段构建,减小镜像体积 +FROM python:3.13-slim AS base +WORKDIR /app + +# 安装 uv +RUN pip install --no-cache-dir uv + +COPY pyproject.toml uv.lock ./ +RUN uv sync --frozen --no-dev + +COPY . . + +# 使用 gunicorn + uvicorn worker 生产部署 +RUN uv pip install "gunicorn[asyncio]>=23.0.0" + +EXPOSE 8000 + +# 生产启动命令:4 个 worker 进程 +CMD ["gunicorn", "main:app", \ + "--workers", "4", \ + "--worker-class", "uvicorn.workers.UvicornWorker", \ + "--bind", "0.0.0.0:8000", \ + "--timeout", "120"] +``` + +### 3.2 docker-compose.yml + +```yaml +version: "3.9" + +services: + app: + build: . + ports: + - "8000:8000" + environment: + - DATABASE_URL=mysql+pymysql://sms_user:SMS_pass@db:3306/sms + depends_on: + db: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/api/teachers/"] + interval: 30s + timeout: 10s + retries: 3 + + db: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: root_pass + MYSQL_DATABASE: sms + MYSQL_USER: sms_user + MYSQL_PASSWORD: SMS_pass + volumes: + - sms_mysql_data:/var/lib/mysql + - ./init.sql:/docker-entrypoint-initdb.d/init.sql # 可选:初始化 SQL + ports: + - "3306:3306" + restart: unless-stopped + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "sms_user", "-pSMS_pass"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + sms_mysql_data: +``` + +### 3.3 启动命令 + +```bash +# 构建并启动 +docker-compose up -d --build + +# 查看日志 +docker-compose logs -f app + +# 停止 +docker-compose down + +# 清理数据卷(慎重) +docker-compose down -v +``` + +--- + +## 4. Nginx 反向代理配置 + +```nginx +server { + listen 80; + server_name sms.example.com; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name sms.example.com; + + # SSL 证书(Let's Encrypt 示例) + ssl_certificate /etc/nginx/ssl/sms.example.com/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/sms.example.com/privkey.pem; + + # 安全头 + add_header X-Frame-Options DENY always; + add_header X-Content-Type-Options nosniff always; + add_header Strict-Transport-Security "max-age=31536000" always; + + # 静态文件 + location /static/ { + proxy_pass http://127.0.0.1:8000; + expires 7d; + add_header Cache-Control "public, immutable"; + } + + # API 请求 + location /api/ { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_read_timeout 60s; + } + + # API 文档(仅内网访问) + location /docs { + allow 10.0.0.0/8; + allow 172.16.0.0/12; + allow 192.168.0.0/16; + deny all; + proxy_pass http://127.0.0.1:8000; + } + + location / { + proxy_pass http://127.0.0.1:8000; + } +} +``` + +--- + +## 5. 生产环境部署 + +### 5.1 部署架构 + +``` + ┌─────────────────┐ + │ Cloudflare / │ + │ CDN + WAF │ + └────────┬────────┘ + │ + ┌────────▼────────┐ + │ Nginx │ ← 反向代理 + HTTPS + │ (port 443) │ + └────────┬────────┘ + │ + ┌──────────────┼──────────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌──────────┐ + │ FastAPI │ │ FastAPI │ │ FastAPI │ ← 多实例(水平扩展) + │ :8000 │ │ :8001 │ │ :8002 │ + │ (Worker1)│ │ (Worker2)│ │ (Worker3)│ + └────┬─────┘ └────┬─────┘ └────┬─────┘ + │ │ │ + └──────────────┼──────────────┘ + ▼ + ┌────────────────┐ + │ MySQL 主从 │ ← 一主两从,读写分离 + │ 主库:3306 │ + │ 从库:3307/3308│ + └────────────────┘ + │ + ┌────────────────┐ + │ Redis │ ← 缓存层(V2.0 规划) + │ :6379 │ + └────────────────┘ +``` + +### 5.2 systemd 服务配置(Linux 直接部署) + +```ini +# /etc/systemd/system/sms.service +[Unit] +Description=Student Manage System API +After=network.target mysql.service + +[Service] +Type=notify +User=sms +Group=sms +WorkingDirectory=/opt/sms +ExecStart=/opt/sms/.venv/bin/gunicorn main:app \ + --workers 4 \ + --worker-class uvicorn.workers.UvicornWorker \ + --bind 127.0.0.1:8000 \ + --timeout 120 \ + --access-logfile - \ + --error-logfile - +Restart=always +RestartSec=10 + +# 环境变量 +Environment="DATABASE_URL=mysql+pymysql://sms_user:SMS_pass@localhost:3306/sms" +Environment="LOG_LEVEL=info" + +# 安全加固 +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/opt/sms/logs + +[Install] +WantedBy=multi-user.target +``` + +```bash +# 启用服务 +sudo systemctl daemon-reload +sudo systemctl enable sms +sudo systemctl start sms +sudo systemctl status sms + +# 查看日志 +sudo journalctl -u sms -f +``` + +--- + +## 6. 监控与告警 + +### 6.1 健康检查端点(待 V2.0 实现) + +```python +@app.get("/health") +async def health_check(db: Session = Depends(get_db)): + """综合健康检查""" + try: + db.execute(text("SELECT 1")) + db_status = "ok" + except Exception as e: + db_status = f"error: {e}" + + return { + "status": "healthy" if db_status == "ok" else "degraded", + "database": db_status, + "timestamp": datetime.utcnow().isoformat(), + } +``` + +### 6.2 关键监控指标 + +| 指标 | 工具 | 阈值建议 | 告警方式 | +|------|------|----------|----------| +| API 响应时间 P99 | Prometheus + Grafana | > 500ms | 邮件/钉钉 | +| 错误率(5xx) | Prometheus | > 1% | 即时告警 | +| 数据库连接数 | MySQL SHOW STATUS | > 80% max_connections | 邮件 | +| 磁盘使用率 | node_exporter | > 85% | 即时告警 | +| 服务可用性 | Uptime Robot / Prometheus | < 99.9% | 即时告警 | + +### 6.3 Prometheus 配置示例 + +```yaml +# prometheus.yml +scrape_configs: + - job_name: 'sms-api' + metrics_path: '/metrics' # FastAPI 内置指标(需安装 prometheus-fastapi-instrumentor) + static_configs: + - targets: ['sms-app:8000'] + + - job_name: 'mysql' + static_configs: + - targets: ['exporter-mysql:9104'] +``` + +--- + +## 7. 日志管理 + +### 7.1 日志级别 + +| 环境 | 级别 | 说明 | +|------|------|------| +| 开发 | DEBUG | 详细调试信息 | +| 测试 | INFO | 常规业务日志 | +| 生产 | WARNING | 仅警告及以上 | + +### 7.2 日志收集方案 + +``` +应用日志 → /var/log/sms/ + ├── access.log # HTTP 请求日志(Nginx upstream_log) + ├── error.log # 错误日志 + └── app.log # 业务日志(loguru) + +日志收集: + 方案A(轻量):logrotate 轮转 + 手动归档 + 方案B(推荐):Filebeat → Logstash → Elasticsearch(ELK) + 方案C(云原生):Fluent Bit → Loki +``` + +### 7.3 logrotate 配置 + +```conf +# /etc/logrotate.d/sms +/opt/sms/logs/*.log { + weekly + rotate 12 + compress + missingok + notifempty + copytruncate +} +``` + +--- + +## 8. 备份与恢复 + +### 8.1 数据库备份策略 + +```bash +#!/bin/bash +# /opt/sms/scripts/backup.sh +BACKUP_DIR="/opt/sms/backups" +DATE=$(date +%Y%m%d_%H%M%S) +DB_NAME="sms" +DB_USER="sms_user" +DB_PASS="SMS_pass" + +mkdir -p "$BACKUP_DIR" + +# 全量备份 +mysqldump -u"$DB_USER" -p"$DB_PASS" \ + --single-transaction \ + --routines \ + --triggers \ + "$DB_NAME" > "$BACKUP_DIR/sms_$DATE.sql" + +# 压缩 +gzip "$BACKUP_DIR/sms_$DATE.sql" + +# 保留最近 30 天 +find "$BACKUP_DIR" -name "sms_*.sql.gz" -mtime +30 -delete + +echo "Backup completed: sms_$DATE.sql.gz" +``` + +### 8.2 定时备份(crontab) + +```cron +# 每天凌晨 2 点备份 +0 2 * * * /opt/sms/scripts/backup.sh >> /var/log/sms/backup.log 2>&1 +``` + +### 8.3 恢复流程 + +```bash +# 查看备份文件 +ls -lh /opt/sms/backups/ + +# 恢复(先停止服务写入) +gunzip < /opt/sms/backups/sms_20250101_020000.sql.gz | mysql -u sms_user -p sms +``` + +--- + +## 9. 故障排查手册 + +### 9.1 常见问题 + +| 现象 | 可能原因 | 排查步骤 | +|------|----------|----------| +| 服务无法启动 | 端口被占用 | `lsof -i :8000` 查看并 kill | +| 数据库连接失败 | URL 错误/网络不通 | 检查 DATABASE_URL,`telnet db_host 3306` | +| API 返回 500 | SQL 错误/模型字段缺失 | 查看 `journalctl -u sms -n 100` | +| 响应慢 | 缺少索引/慢查询 | `SHOW PROCESSLIST`,分析 EXPLAIN | +| 磁盘满 | 日志未轮转/备份堆积 | `du -sh /var/log/sms/`,清理旧日志 | + +### 9.2 快速重启命令 + +```bash +sudo systemctl restart sms +sudo systemctl status sms +sudo journalctl -u sms --since "10 minutes ago" -n 50 +``` + +--- + +## 10. 版本发布流程 + +``` +1. 代码审查(PR merge to main) +2. 自动化测试(CI pipeline) +3. 构建镜像(docker build + push) +4. 部署测试环境(docker-compose up -d) +5. 集成测试通过 +6. 生产灰度发布(先上 1 个实例) +7. 观察 30 分钟无异常 +8. 全量发布(剩余实例滚动更新) +9. 发布报告归档 +``` diff --git a/docs/05-optimization-plan.md b/docs/05-optimization-plan.md new file mode 100644 index 0000000..ee0b8f1 --- /dev/null +++ b/docs/05-optimization-plan.md @@ -0,0 +1,452 @@ +# 学生管理系统 — 优化文档 v1.0 + +> 本文档针对当前 V1.0 代码实现,梳理存在的问题、提出优化方案,按优先级排序,供后续迭代参考。 + +--- + +## 一、当前问题清单 + +### 1. 数据库层 + +#### [P0] database.py 硬编码 SQLite,未支持多数据源 +**现状:** +```python +DATABASE_URL = "sqlite:///./sms.db" +``` +**问题:** 开发/测试/生产环境共用同一配置,切换数据库需改代码重启。 +**影响:** README 中明确规划了 dev→prod 的多数据源适配,当前未实现。 +**优化方案:** +- 引入 `pydantic-settings` 或 `python-dotenv` 读取 `.env` 环境变量 +- 根据 `ENV` 环境变量自动切换连接串 +- 支持 MySQL / PostgreSQL 驱动切换 + +#### [P0] 软删除未更新 delete_time +**现状:** `students.py`、`scores.py`、`employment.py` 中软删除只设 `is_deleted=True`,`delete_time` 始终为 NULL;仅 `teachers.py` 正确设置了 `delete_time`。 +**问题:** 审计字段不一致,删除追溯能力缺失。 +**优化方案:** 统一在 BaseModel 或基类方法中处理 `delete_time` 赋值,所有删除接口复用同一逻辑。 + +#### [P1] 缺少 Alembic 迁移工具 +**现状:** 建表依赖 `Base.metadata.create_all()`,每次启动都会执行,生产环境不安全。 +**问题:** 无法做版本控制、灰度发布、回滚。 +**优化方案:** 引入 Alembic,将 `init_db()` 改为迁移脚本,seed 数据单独管理。 + +#### [P1] 索引设计有冗余和缺失 +**现状:** +- `teachers.num` 同时有 `unique=True`(自带索引)和代码中手动加 `index=True`,重复 +- `students` 表缺少 `(is_deleted, class_id)` 联合索引的显式定义(虽然代码中写了但 ORM 未强制) +- `employment` 表缺少 `(class_id, is_deleted)` 复合索引的实际生效确认 +**优化方案:** 清理重复索引,在模型 `__table_args__` 中统一声明复合索引,与 DDL 文档对齐。 + +--- + +### 2. API 层 + +#### [P0] 缺少统一异常处理和响应格式 +**现状:** 每个 router 独立 `raise HTTPException`,错误响应格式不一致(有的返回 `{"detail": "..."}`,有的返回 plain string)。 +**问题:** 前端难以统一处理错误,移动端/第三方接入体验差。 +**优化方案:** +```python +# 新增 utils/exceptions.py +class SMSException(Exception): + def __init__(self, code: int, message: str, detail: Any = None): + self.code = code + self.message = message + self.detail = detail + +@app.exception_handler(SMSException) +async def sms_exception_handler(request, exc): + return JSONResponse( + status_code=exc.code, + content={"code": exc.code, "message": exc.message, "detail": exc.detail}, + ) +``` + +#### [P0] 分页参数 skip/limit 未做边界校验 +**现状:** `limit` 最大无上限,攻击者可传 `?limit=999999` 导致内存溢出。 +**问题:** 缺乏防护,存在 DoS 风险。 +**优化方案:** +```python +skip: int = Query(0, ge=0), +limit: int = Query(50, ge=1, le=200), # 默认50,最大200 +``` + +#### [P1] 列表接口无总数返回 +**现状:** GET 列表只返回 `List[Model]`,前端无法实现完整分页(无 total count)。 +**问题:** 需要额外发一次 `COUNT` 请求,增加网络开销。 +**优化方案:** 返回 `{ "data": [...], "total": int, "skip": int, "limit": int }` 结构。 + +#### [P1] CORS 全开放,生产不安全 +**现状:** +```python +allow_origins=["*"], allow_credentials=True +``` +**问题:** `*` 与 `allow_credentials=True` 冲突(浏览器会拒绝),且生产环境不应放行所有来源。 +**优化方案:** 通过环境变量配置允许的来源列表,生产环境严格限制。 + +#### [P1] 统计接口存在 N+1 查询问题 +**现状:** `stats.py` 的 `employment_stats` 中,对每个班级单独再发一次查询计算 `avg_duration_days`: +```python +for r in results: + durations = db.query(...).filter(Employment.class_id == r.class_id, ...).all() +``` +**问题:** N 个班级触发 N+1 次查询,数据量增长后性能急剧下降。 +**优化方案:** 一次查询获取所有班级的周期数据,在内存中分组聚合。 + +--- + +### 3. 业务逻辑层 + +#### [P0] 缺少用户认证与权限控制 +**现状:** 所有接口无需任何凭证即可访问和修改数据。 +**问题:** 任何知道 URL 的人都可以增删改查全部数据,存在严重安全隐患。 +**优化方案(V2.0):** +- 引入 JWT OAuth2 认证(FastAPI 内置 `OAuth2PasswordBearer`) +- 基于角色的访问控制(RBAC):教务管理员 / 老师 / 顾问 +- 敏感接口(写操作)强制鉴权,统计接口可匿名访问 + +#### [P1] 缺少数据校验规则 +**现状:** Pydantic Schema 未对关键字段做格式约束。 +**问题示例:** +- `age` 可以为负数或超大值 +- `score` 可以为负数或超过合理范围(如 200 分) +- `num`(编号)无格式校验,可能输入非法字符 +**优化方案:** +```python +from pydantic import Field, field_validator + +class StudentCreate(BaseModel): + age: int = Field(6, ge=6, le=100) + sex: int = Field(0, ge=0, le=2) + score: Decimal = Field(..., ge=0, le=100) + + @field_validator('num') + @classmethod + def validate_num(cls, v): + if not re.match(r'^[A-Za-z0-9_-]{1,30}$', v): + raise ValueError('编号只能包含字母、数字、下划线和短横线') + return v +``` + +#### [P1] 删除操作无二次确认(业务层) +**现状:** 删除学生时,级联删除其所有成绩和就业记录,无前置检查。 +**问题:** 一旦误删,数据不可恢复(软删除也无法区分级联删除和手动删除)。 +**优化方案:** +- 删除学生前检查是否有关联成绩/就业记录,给出警告 +- 提供"彻底删除"和"软删除"两种模式供管理员选择 + +#### [P2] seed 数据硬编码在 Python 中,不可维护 +**现状:** `seed.py` 包含大量内联数据,与 DDL 文档中的造数 SQL 分离。 +**问题:** 两份数据容易不同步,新成员难以快速理解测试数据结构。 +**优化方案:** 将种子数据统一迁移到 SQL 文件(`seed/data.sql`),由 Alembic 迁移或独立脚本导入。 + +--- + +### 4. 前端层 + +#### [P1] 静态页面与后端耦合度高 +**现状:** `static/index.html` 中 `API = 'http://localhost:8000/api'` 硬编码。 +**问题:** 部署到测试/生产环境需改代码重新构建。 +**优化方案:** +- 通过环境变量注入 API 地址:`const API_BASE = window.API_BASE || 'http://localhost:8000/api';` +- 或在 Nginx 层做代理转发,前端直接请求相对路径 `/api/...` + +#### [P1] 缺少加载状态和错误提示 +**现状:** 所有接口调用无 loading 状态,失败时无用户提示。 +**问题:** 用户体验差,网络异常时页面卡死无反馈。 +**优化方案:** 添加统一的请求拦截器,显示 loading spinner 和错误 toast。 + +#### [P2] 前端无路由,单页切换靠 display 控制 +**现状:** 所有模块在一个 HTML 文件中,用 `style.display` 切换。 +**问题:** 无法书签共享特定页面,SEO 不支持,体积随功能增长而膨胀。 +**优化方案:** V2.0 迁移至 Vue3 + Vue Router,组件化开发。 + +--- + +### 5. 运维与工程化 + +#### [P0] 缺少健康检查端点 +**现状:** 无 `/health` 接口,负载均衡器无法检测服务状态。 +**问题:** 容器编排(K8s/Docker Swarm)无法做存活探针,故障实例无法自动剔除。 +**优化方案:** +```python +from sqlalchemy import text + +@app.get("/health") +async def health_check(db: Session = Depends(get_db)): + db.execute(text("SELECT 1")) + return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()} +``` + +#### [P1] 缺少日志系统 +**现状:** 无日志配置,生产环境无法追踪请求链路。 +**问题:** 故障排查只能靠打印到终端,线上无法定位问题。 +**优化方案:** +- 引入 `loguru`,配置请求日志中间件 +- 记录:请求方法、路径、状态码、耗时、客户端 IP +- 日志按天轮转,保留 30 天 + +#### [P1] 缺少单元测试 +**现状:** 无任何测试代码。 +**问题:** 修改功能无法验证回归,重构风险高。 +**优化方案:** +- 引入 `pytest` + `httpx`(异步测试客户端) +- 覆盖核心 CRUD 接口和统计接口 +- CI 流水线中自动运行测试 + +#### [P2] 缺少 API 文档自动化 +**现状:** FastAPI 自动生成 Swagger,但无 Postman/OpenAPI 导出。 +**问题:** 前端联调时无法快速导入接口列表。 +**优化方案:** +- 启动时导出 OpenAPI JSON 到 `docs/openapi.json` +- 提供 Postman Collection 下载端点 + +--- + +## 二、优化实施优先级矩阵 + +| 优先级 | 类别 | 优化项 | 预计工作量 | 建议阶段 | +|--------|------|--------|-----------|----------| +| P0 | 安全 | 用户认证(JWT/OAuth2) | 3-5天 | V2.0 | +| P0 | 安全 | 统一异常处理 | 0.5天 | V2.0 第1周 | +| P0 | 数据 | 多数据源配置(.env) | 1天 | V2.0 第1周 | +| P0 | 数据 | 统一软删除 delete_time | 0.5天 | V2.0 第1周 | +| P0 | 运维 | 健康检查端点 | 0.5天 | V2.0 第1周 | +| P0 | 性能 | 统计接口 N+1 查询修复 | 1天 | V2.0 第2周 | +| P1 | API | 分页参数边界校验 | 0.5天 | V2.0 第1周 | +| P1 | API | 列表接口返回总数 | 1天 | V2.0 第2周 | +| P1 | API | CORS 生产环境收紧 | 0.5天 | V2.0 第1周 | +| P1 | 业务 | 数据校验规则完善 | 2天 | V2.0 第2周 | +| P1 | 业务 | 删除前关联检查 | 1天 | V2.0 第2周 | +| P1 | 数据库 | Alembic 迁移工具 | 2天 | V2.0 第3周 | +| P1 | 数据库 | 索引清理与对齐 | 0.5天 | V2.0 第1周 | +| P1 | 运维 | 日志系统(loguru) | 1天 | V2.0 第2周 | +| P1 | 运维 | 单元测试框架 | 2天 | V2.0 第3周 | +| P2 | 前端 | API 地址环境变量化 | 0.5天 | V2.0 第1周 | +| P2 | 前端 | 加载状态与错误提示 | 1天 | V2.0 第2周 | +| P2 | 前端 | Vue3 重构(可选) | 10-15天 | V3.0 | +| P2 | 运维 | 种子数据 SQL 化 | 1天 | V2.0 第2周 | +| P2 | 运维 | API 文档导出 | 0.5天 | V2.0 第1周 | + +--- + +## 三、V2.0 版本规划(特色需求) + +### 3.1 模块新增 + +| 模块 | 说明 | 接口前缀 | +|------|------|----------| +| 用户认证 | JWT Token 登录/刷新/注销 | `/api/auth` | +| 部门管理 | 组织架构树,老师归属部门 | `/api/departments` | +| 顾问管理 | 顾问老师与学生的绑定关系(当前 advisor_id 已存在,需独立模块化管理) | `/api/advisors` | +| 数据源配置 | 多环境数据库配置管理(仅管理员可见) | `/api/config` | + +### 3.2 统计增强 + +| 功能 | 说明 | +|------|------| +| 趋势分析 | 成绩/就业率按月/季度趋势折线图数据接口 | +| 导出功能 | 统计结果导出为 Excel/CSV | +| 不及格预警 | 连续两次不及格学生自动标记,推送消息 | + +### 3.3 AI 能力(V3.0 预留) + +| 功能 | 说明 | +|------|------| +| 自然语言查询 | 输入"三班平均分多少",自动转为 SQL 查询 | +| 智能报告 | 定期自动生成班级学情报告(PDF) | +| 知识库 | 教培行业常见问题 FAQ 智能问答 | + +--- + +## 四、数据库优化专项 + +### 4.1 字典表落地(V2.0) + +当前 `sex`、`education`、`coach_area` 等字段存的是裸值,需升级为字典表驱动: + +```sql +-- 迁移脚本(在 Alembic migration 中执行) +ALTER TABLE students ADD COLUMN sex_code VARCHAR(10) AFTER sex; +ALTER TABLE students ADD COLUMN education_code VARCHAR(20) AFTER education; +UPDATE students SET sex_code = CAST(sex AS CHAR) WHERE sex IS NOT NULL; +UPDATE students SET education_code = + CASE education + WHEN '本科' THEN 'bachelor' + WHEN '硕士' THEN 'master' + WHEN '博士' THEN 'phd' + ELSE education_code + END; +-- 后续版本再移除裸值字段 +``` + +### 4.2 大表归档策略(V3.0) + +当学生表超过 100 万行时启用: + +```sql +-- 创建归档表 +CREATE TABLE students_archive LIKE students; +CREATE TABLE scores_archive LIKE scores; + +-- 归档毕业超过 2 年的学生 +INSERT INTO students_archive +SELECT * FROM students +WHERE is_deleted = 0 + AND graduate_time IS NOT NULL + AND graduate_time < DATE_SUB(NOW(), INTERVAL 2 YEAR); + +-- 归档后从主表删除(谨慎操作,先备份) +DELETE FROM students +WHERE id IN (SELECT id FROM students_archive); +``` + +--- + +## 五、运维优化专项 + +### 5.1 项目目录结构调整 + +``` +student_manage_system/ +├── app/ # 应用主体(重构后) +│ ├── __init__.py +│ ├── main.py # 应用入口 +│ ├── config.py # 配置管理(.env 读取) +│ ├── database.py # DB 连接 +│ ├── models/ # ORM 模型(按模块分包) +│ │ ├── __init__.py +│ │ ├── teacher.py +│ │ ├── student.py +│ │ ├── class.py +│ │ ├── score.py +│ │ └── employment.py +│ ├── schemas/ # Pydantic Schema +│ │ ├── __init__.py +│ │ └── ... +│ ├── routers/ # API 路由 +│ │ ├── __init__.py +│ │ ├── auth.py # 新增 +│ │ └── ... +│ ├── services/ # 业务逻辑层(新增) +│ │ └── ... +│ └── utils/ # 工具函数 +│ ├── exceptions.py # 新增:统一异常 +│ └── logging.py # 新增:日志配置 +├── tests/ # 新增:测试目录 +│ ├── __init__.py +│ ├── test_teachers.py +│ ├── test_students.py +│ └── conftest.py +├── alembic/ # 新增:数据库迁移 +│ ├── versions/ +│ └── env.py +├── seed/ # 新增:种子数据 SQL +│ └── data.sql +├── static/ # 保持现有 +├── docs/ # 保持现有 +├── .env.example # 新增:环境变量模板 +├── .env.local # 新增:本地开发配置(.gitignore) +├── Dockerfile +├── docker-compose.yml +├── pyproject.toml +└── README.md +``` + +### 5.2 .env 配置模板 + +```env +# 环境:development / staging / production +APP_ENV=development + +# 数据库 +DATABASE_URL=mysql+pymysql://sms_user:SMS_pass@db-host:3306/sms +# 或开发环境 +# DATABASE_URL=sqlite:///./sms.db + +# 应用 +APP_HOST=0.0.0.0 +APP_PORT=8000 +DEBUG=true + +# JWT(生产必须配置) +SECRET_KEY=your-random-secret-key-here +ALGORITHM=HS256 +ACCESS_TOKEN_EXPIRE_MINUTES=1440 + +# CORS(生产填写具体域名) +ALLOWED_ORIGINS=https://sms.example.com,https://admin.sms.example.com + +# 日志 +LOG_LEVEL=INFO +LOG_FILE=/var/log/sms/app.log +``` + +### 5.3 Docker Compose 生产版 + +```yaml +version: "3.9" + +services: + app: + build: . + environment: + - DATABASE_URL=${DATABASE_URL} + - SECRET_KEY=${SECRET_KEY} + - ALLOWED_ORIGINS=${ALLOWED_ORIGINS} + volumes: + - ./logs:/app/logs + depends_on: + db: + condition: service_healthy + restart: unless-stopped + + db: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: sms + MYSQL_USER: sms_user + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + volumes: + - sms_mysql_data:/var/lib/mysql + - ./seed/data.sql:/docker-entrypoint-initdb.d/init.sql + ports: + - "127.0.0.1:3306:3306" # 仅本地可访问 + restart: unless-stopped + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "sms_user", "-p${MYSQL_PASSWORD}"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + sms_mysql_data: + driver: local +``` + +--- + +## 六、接口变更清单(V2.0) + +| 接口 | 变更类型 | 变更内容 | +|------|----------|----------| +| `POST /api/auth/login` | **新增** | 用户名密码登录,返回 JWT | +| `POST /api/auth/refresh` | **新增** | 刷新 Token | +| `POST /api/auth/logout` | **新增** | 注销(黑名单 Token) | +| `GET /api/teachers/` | **修改** | 需要 Token,返回加上 `total` 字段 | +| `GET /api/students/` | **修改** | 需要 Token,分页参数加 `ge/le` 校验 | +| `GET /api/stats/classes` | **修改** | 修复 N+1 查询,性能优化 | +| `GET /health` | **新增** | 健康检查端点 | + +--- + +## 七、总结 + +| 维度 | 当前状态 | V2.0 目标 | +|------|----------|-----------| +| 安全性 | 无认证,CORS 全开放 | JWT 认证 + RBAC + 严格 CORS | +| 数据一致性 | 软删除字段不完整 | 全量统一审计字段 | +| 性能 | 统计接口 N+1 查询 | 单次聚合查询 | +| 可维护性 | 单体结构,无测试 | 分层架构 + 单元测试覆盖 | +| 可观测性 | 无日志无监控 | loguru 日志 + health endpoint | +| 部署 | 手动启动 | Docker + .env 配置化管理 |