# 学生管理系统 — 优化文档 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 配置化管理 |