# 审计 — 服务器搜索历史 MySQL 持久化

## 范围（文件清单）

| 文件 | 变更 |
|------|------|
| `server/domain/models/__init__.py` | 新增 `AdminUiPreference` |
| `server/infrastructure/database/admin_ui_preference_repo.py` | 读写偏好 JSON |
| `server/infrastructure/database/migrations.py` | 建表 `admin_ui_preferences` |
| `server/utils/server_search_history.py` | 去重/上限 12 条 |
| `server/api/servers.py` | `GET/PUT /api/servers/search-history` |
| `server/api/schemas.py` | `ServerSearchHistoryUpdate` |
| `frontend/src/composables/useServerSearchHistory.ts` | API + localStorage 一次性迁移 |
| `frontend/src/pages/ServersPage.vue` | combobox 搜索历史 |
| `tests/test_server_search_history.py` | 单元 + API 往返 |

## Step 3 规则扫描

| H | 规则 | 结论 |
|---|------|------|
| H1 | 路由在 `/{id}` 之前注册 | PASS — `search-history` 静态路径优先 |
| H2 | 仅当前管理员可读写 | PASS — `get_current_admin` + `admin_id` |
| H3 | 无静默吞错 | PASS — API 异常向上抛出 |
| H4 | 无跨层直连 SQL | PASS — 经 `admin_ui_preference_repo` |
| H5 | 输入校验 | PASS — Pydantic + 归一化 strip/上限 |

## Closure

| H | 判定 | 依据 |
|---|------|------|
| H1 | PASS | `servers.py` 路由顺序 |
| H2 | PASS | repo 按 `admin_id` + `context` |
| H3 | PASS | 无 bare except |
| H4 | PASS | 分层合规 |
| H5 | PASS | `server_search_history.py` |

## DoD

- [x] `pytest tests/test_server_search_history.py` 4 passed
- [x] `npm run type-check` 通过
- [x] changelog `2026-06-11-servers-search-history.md`
- [x] migration 启动时建表

## 验证

Servers 页搜索 → 刷新 → 历史仍在；换浏览器同账号 → GET 返回相同 `history`/`last`。
