Files
gaokao-volunteer-system/docs/plans/T6-admin-mvp.md
Hermes Agent 3f46811e49
Some checks failed
CI / pytest (Python 3.10) (push) Has been cancelled
CI / pytest (Python 3.11) (push) Has been cancelled
CI / pytest (Python 3.12) (push) Has been cancelled
release: cut v2.1
2026-06-13 14:49:58 +08:00

16 KiB
Raw Blame History

T6 管理后台 MVP — 详细设计

状态: v2.1 实施设计 最后更新: 2026-06-12 关联文档: IMPLEMENTATION_PLAN_v2.md, TECH_ARCHITECTURE.md 关联任务: T6.1 FastAPI 后端基础 / T6.2 仪表盘 / T6.3 用户管理 / T6.4 订单管理 / T6.5 案例管理 / T6.6 简易 WebUI / T6.7 Docker 部署


1. 设计目标

1.1 总目标

为运营人员提供一个最小可用的 Web 管理后台,承接订单/用户/案例/审核报告的核心 CRUD + 统计展示,覆盖 2026 高考季6/25-7/5的运营需求。

1.2 非目标

  • 复杂 RBAC 权限体系MVP 用 admin 单一角色,后续 T8 扩展)
  • 重前端Vue/React—— 走 ECharts + 纯 HTML/JSv2.1 不引前端框架)
  • 高并发/集群部署 —— 用户基数小(目标 100+ 考生),单机 SQLite 足够
  • 完整 OpenAPI 兼容性 —— 自动 Swagger 文档即可

1.3 关键原则

原则 说明
本地优先 默认本地 SQLite云端可选
零侵入宿主 不依赖外部子项目,仅调用本项目 data/orders/ 已实现层
测试优先 每个路由有 test client 测试;覆盖率目标 >= 70%
最小风险闭环 T6.1 只做骨架(启动+路由+JWT+Swagger业务路由在 T6.2-T6.5 增量实现
明文/加密分字段 API 层明文接收,落盘前调用 T4.1 加密层

2. 技术选型v2.1 决策)

维度 选择 理由
Web 框架 FastAPI 0.133 自动 OpenAPI、依赖注入、类型驱动
ASGI 服务器 uvicorn 0.41 标配 FastAPI 部署
认证 PyJWT 2.13 (HS256) 标准 JWT密钥从环境变量读取
ORM 不使用 SQLAlchemy 保持项目"零运行时第三方"约束;用 stdlib sqlite3
Pydantic Pydantic v2.13 FastAPI 强依赖;用 BaseModel 做请求/响应模型
密码哈希 stdlib hashlib.pbkdf2_hmac 不引 bcrypt/argon2MVP 简化)
测试 pytest + httpx TestClient httpx 0.28 已装

为什么不用 SQLAlchemy:项目 README/requirements-dev.txt 明确"运行时本项目为纯标准库实现,无第三方运行时依赖"。T4.1 已用 stdlib sqlite3 + 自写 dataclass。引入 SQLAlchemy 会破坏这个约束。DAO 层用 sqlite3 + dataclass 即可覆盖 CRUD。


3. 模块结构

admin/
├── __init__.py             # 模块导出
├── app.py                  # FastAPI app 工厂 (create_app)
├── config.py               # 配置加载(环境变量 + .env 兜底)
├── db.py                   # SQLite 连接管理 + DAO 工厂
├── auth.py                 # JWT 签发/校验/依赖
├── password.py             # PBKDF2 哈希
├── routes/
│   ├── __init__.py
│   ├── health.py           # GET /health (公开)
│   ├── auth.py             # POST /api/auth/login, GET /api/auth/me
│   ├── orders.py           # T6.4 订单管理(列表/详情/录单/PATCH/导出)
│   ├── stats.py            # GET /api/stats/orders (骨架,T6.2 完善)
│   └── meta.py             # GET /api/meta (province 列等)
└── tests/
    ├── __init__.py
    ├── conftest.py         # FastAPI TestClient + 内存 SQLite fixture
    ├── test_app.py         # app 启动 + Swagger 可达
    ├── test_auth.py        # JWT 签发/校验 + 401 路径
    ├── test_routes_health.py
    ├── test_routes_auth.py
    └── test_routes_orders.py

3.1 顶层路由v2.1 MVP 阶段)

方法 路径 鉴权 说明 阶段
GET /health 公开 健康检查DB + 版本) T6.1
GET /docs 公开 Swagger UIFastAPI 自动) T6.1
GET /openapi.json 公开 OpenAPI Schema T6.1
POST /api/auth/login 公开 用户名+密码 → JWT T6.1
GET /api/auth/me JWT 当前用户信息 T6.1
GET /api/orders JWT 订单列表(真实 DAO + 脱敏) T6.4
GET /api/orders/export JWT CSV 导出(默认脱敏) T6.4
GET /api/orders/{id} JWT 订单详情 + 状态历史 T6.4
POST /api/orders JWT 手工录单external_id 可空) T6.4
PATCH /api/orders/{id} JWT 业务字段更新 / 状态流转 / 退款 T6.4
GET /api/stats/orders JWT 订单统计(占位,空对象) T6.1
GET /api/meta JWT 元数据(省份列等) T6.1

T6.2 / T6.3 / T6.4 已分别落地仪表盘、用户管理与订单管理;后续剩余案例 CRUD、登录页 + 列表页等增量任务。


4. 数据模型

4.1 复用 T4.1

  • data/orders/models.py::Order — 订单 dataclass
  • data/orders/schema.py::apply_schema — DDL
  • data/orders/crypto.py::encrypt/decrypt/hash_for_index — 字段加密
  • data/orders/state_machine.py::OrderStatus — 6 态枚举

4.2 新增admin_users 表T6.1 引入)

CREATE TABLE IF NOT EXISTS admin_users (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    username        TEXT UNIQUE NOT NULL,
    password_hash   TEXT NOT NULL,      -- 存储格式: <salt_hex>$<pbkdf2_hash_hex>
    role            TEXT NOT NULL DEFAULT 'admin',  -- 后续 T8 RBAC 扩展
    is_active       INTEGER NOT NULL DEFAULT 1,
    created_at      TEXT NOT NULL,
    last_login_at   TEXT
);
CREATE INDEX IF NOT EXISTS idx_admin_users_username ON admin_users(username);

4.3 决策点

  • 只允许单 admin? — 支持多账户(运营多人轮班)
  • 密码存储? PBKDF2-HMAC-SHA256, 200k 迭代, 16B salt → 64B hash
  • 默认账户? 启动时若 admin_users 为空,自动创建 admin/admin123,并在日志 WARN 提示修改

5. JWT 设计

5.1 Token 结构HS256

{
  "sub": "admin:1", // 用户标识
  "username": "admin",
  "role": "admin",
  "iat": 1718172800,
  "exp": 1718176400 // 默认 1 小时
}

5.2 配置

环境变量 默认值 说明
GAOKAO_JWT_SECRET dev-only 占位(启动时 WARN HS256 密钥(>=32B
GAOKAO_JWT_EXP_MIN 60 过期时间(分钟)
GAOKAO_DB_PATH data/orders/admin.db SQLite 路径
GAOKAO_ENV dev dev/prod 标识

安全警告:生产部署必须显式设置 GAOKAO_JWT_SECRET 为高熵随机值。MVP 阶段在 dev 环境用占位即可,但启动日志必须明显提示。

5.3 中间件行为

  • 请求带 Authorization: Bearer <token> → 解析 token,设置 request.state.user
  • 无 token / 过期 / 签名错 → 返回 401 WWW-Authenticate: Bearer
  • 受保护路由通过 Depends(get_current_user) 获取当前用户
  • 公开路由health, login, docs, openapi走白名单

6. Swagger / OpenAPI

6.1 自动生成

FastAPI 自动生成 /docs (Swagger UI) 和 /openapi.json。无需手写。

6.2 元信息增强

app = FastAPI(
    title="高考志愿填报管理后台 API",
    version="0.1.0",
    description="""管理后台 MVP API。
    认证:POST /api/auth/login → Bearer JWT。
    详细字段:data/orders/models.py""",
    contact={"name": "Hermes Agent"},
    license_info={"name": "MIT"},
)

6.3 标签分组

  • health — 健康检查
  • auth — 认证
  • orders — 订单
  • stats — 统计

7. 测试策略TDD

7.1 测试框架

  • pytest + fastapi.testclient.TestClient(内部用 httpxdev 依赖需显式安装)
  • fixture内存 SQLitesqlite3.connect(':memory:')+ 自动建表 + 自动建 admin 账户
  • 每个测试函数独立 DB避免状态污染

7.2 必测项

验证
GET /health 200 + {"status":"ok"}
GET /docs 200 HTML
GET /openapi.json 200 JSON, 含 paths
POST /api/auth/login 正确凭证 200 + JWT
POST /api/auth/login 错凭证 401
GET /api/auth/me 无 token 401
GET /api/auth/me 有效 token 200 + 用户信息
GET /api/orders 无 token 401
GET /api/orders 有效 token 200 + [] (MVP 阶段无数据)
GET /api/orders/{id} 不存在 404
JWT 过期 401
JWT 签名错 401
pytest --cov admin 覆盖率 >= 70%

7.3 覆盖率门槛

参考 v2.1 实施计划:核心包覆盖率 >= 70%。T6.1 完成后跑:

cd /home/long/project/gaokao-volunteer-system
python3 -m pytest admin/tests/ --cov=admin --cov-report=term-missing

8. 部署

8.1 MVP 本地启动

# 1. 安装依赖(一次性)
pip3 install --user --break-system-packages fastapi uvicorn pyjwt httpx pydantic

# 2. 启动服务
export GAOKAO_JWT_SECRET="$(python3 -c 'import secrets;print(secrets.token_hex(32))')"
python3 -m admin.app --port 8000

# 3. 访问
open http://localhost:8000/docs

8.2 T6.7 Docker 阶段(不在 T6.1 范围)

T6.7 阶段出 Dockerfile + docker-compose.yml。


9. 风险与缓解

风险 概率 影响 缓解
FastAPI 升级破坏兼容 锁版本fastapi==0.133.1, pydantic==2.13.4
默认 admin 账户泄露 启动日志 WARN + README 强调 prod 必须改密
JWT secret 弱 dev 占位 + 启动日志断言长度
SQLite 并发写冲突 MVP 单机 + 启用 WAL
与 T4.1 dataclass 耦合过紧 DAO 层明确接口边界

10. T6.2 仪表盘(一站式数据统计)

10.1 设计目标

为运营人员提供一个最小可用的仪表盘端点:返回订单数 / 用户数 / 收入三项汇总 卡片,叠加 6 态分布 / 来源分布 / 服务版本分布三个柱状图数据源,并给出 今日 / 7 天 / 30 天三个窗口的趋势序列日粒度0 填充)。

10.2 端点

方法 路径 鉴权 说明
GET /api/stats/dashboard JWT 一站式仪表盘 payload
GET /api/stats/orders JWT 订单维度统计(沿用 T6.1 stub 字段名,接入真实 SQL

10.3 响应契约

{
  "summary": {
    "total_orders": 6, "total_revenue_cents": 100000, "total_users": 1,
    "orders_today": 3, "orders_7d": 4, "orders_30d": 5,
    "revenue_today_cents": 20000, "revenue_7d_cents": 70000, "revenue_30d_cents": 100000
  },
  "by_status":         {"pending": 0, "paid": 1, "serving": 1, "delivered": 0, "completed": 1, "refunded": 1},
  "by_source":         {"xianyu": 0, "wechat": 0, "web": 0, "school": 0},
  "by_service_version":{"audit": 0, "basic": 0, "standard": 0, "premium": 0},
  "trends": {
    "today": [{"date": "2026-06-12", "orders": 3, "revenue_cents": 20000}],
    "7d":    [{"date": "2026-06-06", "orders": 0, "revenue_cents": 0}, ...  7 个点 ...],
    "30d":   [{"date": "2026-05-14", "orders": 0, "revenue_cents": 0}, ...  30 个点 ...]
  },
  "generated_at": "2026-06-12T16:30:00+00:00"
}

10.4 口径约定(必须文档化)

  • 收入 (revenue_cents) = 所有 非 pending 且非 refunded 订单的 amount_cents 累计值。
    • pending:未付款,不计入有效收入
    • refunded:已退款,从累计收入中扣除
    • paid / serving / delivered / completed:四态均计入
  • 趋势桶粒度 = 日UTCYYYY-MM-DD)。substr(created_at, 1, 10) 与 ISO8601 字符串前缀等价。
  • "今日" = 服务器当前 UTC 日;7d / 30d = 含今日回溯 7 / 30 个完整日。
  • 0 填充:窗口内的"无订单日"也要返回 0 点,前端拿到的是稠密序列。
  • 数据源隔离
    • orders / order_status_historyGAOKAO_ORDERS_DB_PATH(默认 data/orders.db
    • admin_usersGAOKAO_DB_PATH(默认 data/orders/admin.db
  • 不读 PII:统计路径只触碰 amount_cents / status / source / service_version / created_at,不进入加密层。

10.5 模块分层

  • admin/stats.py:纯函数 SQL 聚合层(build_dashboard_payload / compute_summary / compute_by_* / compute_trends / generate_day_series
    • 可单测:纯函数 + 可注入 today 参数
    • 与路由层解耦:业务测试不依赖 FastAPI
  • admin/routes/stats.pyFastAPI 路由层
    • 仅鉴权 + 响应包装
    • DashboardResponse / OrderStatsResponse 两个 pydantic 模型

10.6 配置新增

  • Settings.orders_db_pathGAOKAO_ORDERS_DB_PATH,默认 data/orders.db
    • data.orders.* 共享同一 DB
    • data.channel_sync.webhook_server 已有的同名环境变量对齐

10.7 测试覆盖11 用例)

admin/tests/test_routes_stats_dashboard.py

  • 鉴权(无 token 401 / 有 token 200
  • 空库形状契约summary / by_* / trends 三层结构稳定
  • 趋势序列1 / 7 / 30 个点,按日期严格升序
  • 0 填充点:包含完整三字段(date / orders / revenue_cents
  • 真实数据窗口边界45 天前的订单被 30d 窗口排除,但计入 total
  • 收入口径pending / refunded 不计入 revenue
  • 0 填充:范围内无订单的日也返回 0 点
  • 兼容层:/api/stats/orders 字段名不变,_stub 标记已移除

10.8 T6.2 DoD

  • admin/stats.py 纯函数聚合层落地
  • /api/stats/dashboard + /api/stats/orders 双端点接入
  • 11 个 dashboard 测试 + 1 个真实数据兼容测试,全部通过
  • admin 全量 87/87 测试通过repo 全量 412/412 测试通过
  • 新增代码 ruff check 通过
  • 文档同步:本文档新增 §10、README 增量更新、CHANGELOG 记录

11. 完成标准DoD

T6.1 完成的硬标准:

  • 设计文档(本文件)
  • admin/ 模块代码app/config/db/auth/password/routes
  • 测试套件通过(覆盖率 >= 70%
  • python3 -m admin.app 启动成功
  • http://localhost:8000/docs 可访问
  • http://localhost:8000/openapi.json 返回合法 OpenAPI schema
  • POST /api/auth/login + GET /api/auth/me 端到端通
  • 默认 admin 账户创建,WARN 日志输出
  • requirements-dev.txt 或新 requirements-admin.txt 列出依赖
  • AGENTS.md / README.md 增量更新T6.1 章节)
  • git commit + push 三仓库

12. 后续任务衔接

  • T6.2 (仪表盘)/api/stats/dashboard + /api/stats/orders 已落地(见 §10
  • T6.3 (用户管理)/api/admin/users 列表 / 详情 / 脱敏 / 搜索已落地
  • T6.4 (订单管理)/api/orders 已补齐列表 / 详情 / POST / PATCH / export状态变化统一走 state_machine
  • T6.5 (案例管理):新增 data/cases/ 表 + /api/cases CRUD
  • T6.6 (WebUI)HTML 模板 + 静态托管
  • T6.7 (Docker)Dockerfile + compose