Skip to content

本教程演示如何从 0 创建一个最小可用的“关卡插件”(一个关卡模块),并接入关卡门禁与发放 token 的流程。

1. 创建关卡目录

在项目根目录下创建:

levels/<module>/
└── __init__.py

其中:

  • <module>:Python 包名(建议与 slug 一致,如 cipher

也可以用 TUI 生成模板:

bash
python manage.py

在 TUI 里按 n 新建关卡,会自动生成 levels/<module>/__init__.py 并把条目追加到 config/map.json

2. 在 config/map.json 注册关卡

打开 config/map.json,在 levels 数组中追加一项:

json
{ "slug": "cipher", "module": "cipher", "name": "凯撒", "enabled": true }

要点:

  • 数组顺序就是等级顺序:索引 0 → Lv0(免 token),索引 1 → Lv1(需要 token)……
  • slug 决定访问路径:/cipher(静态页)与 /api/cipher(API)

3. 编写最小关卡代码(router + verify)

levels/cipher/__init__.py 中提供:

  • router = APIRouter()
  • set_level_index(index: int)CURRENT_LEVEL_INDEX(用于计算下一关)
  • GET /:返回页面(HTML)
  • POST /verify:校验答案并发 token

参考模板(与 manage.py 生成的结构一致):

py
from uuid import uuid4

from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse

from core.auth import generate_token
from core.config import next_level_slug

router = APIRouter()
CURRENT_LEVEL_INDEX = 0

def set_level_index(index: int) -> None:
    global CURRENT_LEVEL_INDEX
    CURRENT_LEVEL_INDEX = index

@router.get("/", response_class=HTMLResponse)
async def render_stage() -> str:
    return "<h1>Cipher</h1>"

@router.post("/verify")
async def verify_answer(request: Request, data: dict) -> dict:
    if (data.get("answer") or "").strip() == "matrix":
        next_level_index = CURRENT_LEVEL_INDEX + 1
        next_slug = next_level_slug(CURRENT_LEVEL_INDEX)
        player = getattr(request.state, "player", {}) or {}
        user_id = player.get("uid") or str(uuid4())
        token = generate_token(request=request, user_id=user_id, level=next_level_index)
        return {"status": "success", "next_url": f"/{next_slug}" if next_slug else None, "token": token}
    return {"status": "wrong"}

4. (可选)添加静态页面

如果你更喜欢把页面放在静态资源里:

levels/cipher/static/index.html

框架会把 static/ 自动挂到 /{slug}(例如 /cipher)。你可以在 index.html 里调用 /api/cipher/verify

5. 运行与验证

启动:

bash
uvicorn main:app --reload

验证路径:

  • 直接访问页面:GET /cipher(如果存在 static/)或 GET /api/cipher/(如果你用 router 返回 HTML)
  • 校验答案:POST /api/cipher/verify(成功应返回 tokennext_url

常见坑

  • “明明写了关卡但访问 404”:检查 config/map.json 是否有该条目且 enabled: true,以及 slug/module 拼写一致。
  • “下一关跳错/门禁不一致”:顺序以 config/map.json 数组顺序为准,不要依赖 id
  • “Lv1+ 请求被拦截”:确认请求携带 Authorization: Bearer <token>,且 token 的 lvl ≥ 当前关卡索引。