17 KiB
学生管理系统 — 优化文档 v1.0
本文档针对当前 V1.0 代码实现,梳理存在的问题、提出优化方案,按优先级排序,供后续迭代参考。
一、当前问题清单
1. 数据库层
[P0] database.py 硬编码 SQLite,未支持多数据源
现状:
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)。
问题: 前端难以统一处理错误,移动端/第三方接入体验差。
优化方案:
# 新增 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 风险。
优化方案:
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 全开放,生产不安全
现状:
allow_origins=["*"], allow_credentials=True
问题: * 与 allow_credentials=True 冲突(浏览器会拒绝),且生产环境不应放行所有来源。
优化方案: 通过环境变量配置允许的来源列表,生产环境严格限制。
[P1] 统计接口存在 N+1 查询问题
现状: stats.py 的 employment_stats 中,对每个班级单独再发一次查询计算 avg_duration_days:
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(编号)无格式校验,可能输入非法字符 优化方案:
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)无法做存活探针,故障实例无法自动剔除。
优化方案:
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 等字段存的是裸值,需升级为字典表驱动:
-- 迁移脚本(在 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 万行时启用:
-- 创建归档表
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 配置模板
# 环境: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 生产版
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 配置化管理 |