Files
mc-gateway/docs/admin-page-design.md
tursom ecb682bb41
Some checks failed
Docker Image / docker (push) Has been cancelled
Refactor admin frontend build
2026-06-25 21:16:11 +08:00

20 KiB
Raw Permalink Blame History

管理页面设计

背景

当前 gateway 已经具备 TCP 与 HTTP/WebSocket 的端口复用能力:

  • Minecraft 原始 TCP 连接进入 handleRequest,再按握手包里的 host 路由到上游。
  • WebSocket 连接先进入 HTTP handler再 upgrade 为 WebSocket最后同样复用 handleRequest
  • tcp_web_port_reuse.go 已经提供首包识别、连接回放和 http.Server.Serve(chanListener) 这条分流路径。

新的管理方案不再以 config.toml 为中心。进程默认启动即可工作:

  • 默认在 25565 端口启动 Minecraft TCP 监听。
  • 同一个 25565 端口同时承载后台管理页面和管理 API。
  • 用户、权限、路由、其他协议服务配置都持久化到 SQLite。
  • 其他服务和路由通过后台管理配置,不再依赖配置文件。

功能需求对齐

第一版管理页面按以下边界设计。

必须支持

  • 进程启动不依赖配置文件;缺省状态下直接监听 25565
  • TCP 转发和后台管理默认共用同一个 TCP listener。
  • 管理入口必须有认证保护。复用公网 Minecraft 端口时,不能出现无认证管理 API。
  • 管理页面内置用户和权限系统,角色分为管理员、成员和游客。
  • 所有持久化状态进入 SQLite包括用户、路由、服务配置和审计日志。
  • 页面提供一个实际可用的后台界面,而不是只返回 JSON
    • 概览:进程 PID、运行时长、SQLite 路径、TCP/Admin 端口、各协议服务状态。
    • 路由管理:展示、搜索、新增、修改、删除 SQLite 中的路由记录。
    • 服务管理:配置 KCP、QUIC、WebSocket 的启用状态、端口和协议参数。
    • 用户管理:管理员可以新增、禁用、修改角色和重置密码。
    • 连接统计:展示总连接数、当前活跃连接数、路由命中次数、上游连接失败次数。
  • 游客登录后可以查看当前 SQLite 路由记录,但不能修改路由、配置服务或查看用户列表。
  • 路由记录必须持久化到 SQLite进程重启后仍然生效。
  • 服务配置必须持久化到 SQLite进程重启后按后台配置恢复。
  • 所有写操作需要记录审计日志,至少包含操作者、来源 IP、操作类型、目标对象和结果。

暂不支持

  • 不再支持 config.toml 作为启动配置、热加载配置或路由来源。
  • 不做多租户、组织空间和自定义细粒度权限。
  • 不做 HTTPS/TLS 终止;需要 HTTPS 时由外部反向代理或负载均衡器处理。
  • 不做 Minecraft 玩家在线列表、踢人、封禁等游戏服管理能力。
  • 不做插件管理、二进制升级、进程重启。
  • 不把每条连接的完整客户端地址、目标地址长期保存在内存里。

默认启动设计

无配置文件时使用以下默认值:

默认值 环境变量
SQLite 数据库 mc-gateway.sqlite3 MC_GATEWAY_DB
TCP/Admin 监听端口 25565 MC_GATEWAY_TCP_ADMIN_PORT
Admin 页面路径 /admin/ MC_GATEWAY_ADMIN_PATH
Admin API 前缀 /admin/api MC_GATEWAY_ADMIN_API_PREFIX
Admin 静态目录 cmd/gateway/admin_static MC_GATEWAY_ADMIN_STATIC_DIR
KCP 默认禁用 后台配置
QUIC 默认禁用 后台配置
WebSocket 默认禁用 后台配置
路由表 默认空表 后台配置
会话有效期 8 小时 后台配置

启动期环境变量规则:

  • MC_GATEWAY_TCP_ADMIN_PORT 只在启动时读取,必须是 1-65535 的整数;为空时使用 25565
  • MC_GATEWAY_ADMIN_PATH 只在启动时读取,必须以 / 开头,规范化为以 / 结尾;为空时使用 /admin/
  • MC_GATEWAY_ADMIN_API_PREFIX 只在启动时读取,必须以 / 开头,规范化为不以 / 结尾;为空时使用 /admin/api
  • MC_GATEWAY_ADMIN_API_PREFIX 不能等于 MC_GATEWAY_ADMIN_PATH,也不能落在静态资源路径下,例如 config.jsjs/
  • MC_GATEWAY_ADMIN_STATIC_DIR 指向 Admin 前端构建产物目录Docker 镜像内使用 /usr/share/mc-gateway/admin_static
  • 环境变量覆盖的是本次进程的 Admin 入口;第一次创建 SQLite 默认服务配置时,应把解析后的 TCP/Admin 端口写入 services.tcp_admin.port

启动流程:

  1. 读取并校验启动期环境变量,得到 SQLite 路径、TCP/Admin 端口、Admin 页面路径、Admin API 前缀。
  2. 打开 SQLite 数据库;不存在时自动创建。
  3. 执行 schema migration。
  4. 确保默认服务配置存在TCP/Admin listener 启用,端口为启动期解析后的端口。
  5. 如果用户表为空,进入首次初始化模式。
  6. 加载启用的路由记录为内存只读快照。
  7. 启动 TCP/Admin 共享 listener。
  8. 按 SQLite 中的服务配置启动 KCP、QUIC、WebSocket。

首次初始化:

  • 当用户表为空时,启动期解析后的 Admin 页面路径显示初始化管理员页面。
  • 初始化接口只允许创建第一个管理员账号。
  • 第一个管理员创建成功后,初始化接口永久关闭。
  • 也可以通过环境变量 MC_GATEWAY_ADMIN_PASSWORD 配合默认用户名 admin 在启动时创建初始管理员;这不是配置文件,只是无交互部署入口。

SQLite 路径:

  • 默认使用当前工作目录下的 mc-gateway.sqlite3
  • 如需改路径,第一版只接受启动参数或环境变量,例如 MC_GATEWAY_DB;不引入配置文件。

运行模型

TCP/Admin listener 是基础入口:

net.Listen(:startup env port or db service setting)
        |
        v
Accept
        |
        v
读取首批字节
        |
        +-- HTTP/Admin/WebSocket
        |       -> HTTP channel listener
        |       -> http.Server.Serve
        |
        +-- Minecraft TCP
                -> 回放首批字节
                -> handleRequest
                -> mapToHost
                -> route snapshot
                -> proxyConnections

核心规则:

  • TCP/Admin listener 默认永远启用,避免后台管理入口丢失。
  • TCP/Admin 端口可以由启动期环境变量覆盖,也可以保存在 SQLite最终监听端口以启动期解析结果优先。
  • TCP/Admin 端口修改后第一版按重启后生效处理。
  • Admin 页面路径和 API 前缀只由启动期环境变量控制,不通过后台页面修改,避免运行中替换管理入口导致当前会话失效。
  • KCP、QUIC、WebSocket 由后台管理配置启用状态和端口。
  • 如果某个服务的端口或协议参数无法热更新,页面必须标记为“重启后生效”或提供明确的重启服务操作。
  • 路由变更不需要重启,也不需要 reloadSQLite 写入成功后刷新路由快照即可影响新连接。

HTTP 路由与页面

Admin 前端源码使用 TypeScript 拆分,构建为原生 ES modules。Go 不再 embed 前端文件,而是从 MC_GATEWAY_ADMIN_STATIC_DIR 指向的目录读取并透传静态响应。运行时 API 前缀通过动态 config.js 响应注入。

建议目录:

cmd/gateway/admin.go
cmd/gateway/admin_api.go
cmd/gateway/admin_auth.go
cmd/gateway/admin_db.go
cmd/gateway/admin_static.go
cmd/gateway/admin_frontend/src/
cmd/gateway/admin_static/
  index.html
  app.css
  js/

API 路由:

方法 路径 权限 说明
GET /admin/ 公开页面 管理页面入口或首次初始化页面
GET /admin/app.css 公开页面 页面样式
GET /admin/config.js 公开页面 运行时前端配置
GET /admin/js/main.js 公开页面 页面脚本入口
POST /admin/api/setup 仅用户表为空 创建第一个管理员
POST /admin/api/auth/login 未登录 用户名密码登录
POST /admin/api/auth/logout 已登录 注销当前会话
GET /admin/api/me 已登录 当前用户、角色和权限
GET /admin/api/status 成员、管理员 进程、端口、数据库、服务状态
GET /admin/api/routes 游客、成员、管理员 当前 SQLite 路由列表
PUT /admin/api/routes/{host} 成员、管理员 新增或更新一条路由
DELETE /admin/api/routes/{host} 成员、管理员 删除一条路由
GET /admin/api/services 成员、管理员 服务配置和运行状态
PUT /admin/api/services/{name} 管理员 修改 KCP、QUIC、WebSocket 配置
POST /admin/api/services/{name}/restart 管理员 重启指定服务
GET /admin/api/metrics 成员、管理员 连接和路由统计
GET /admin/api/users 管理员 用户列表
POST /admin/api/users 管理员 新增用户
PATCH /admin/api/users/{username} 管理员 修改用户角色、状态或密码
DELETE /admin/api/users/{username} 管理员 删除用户
GET /admin/api/audit-logs 管理员 审计日志

页面形态:

  • 用户表为空时只显示初始化管理员界面。
  • 未登录且已初始化时只显示登录界面。
  • 成员和管理员顶部固定显示 gateway 状态、SQLite 路径和服务状态。
  • 路由表支持按 host、upstream、启用状态搜索。
  • 游客登录后只展示当前路由表,不展示新增、编辑、删除、服务配置、用户管理入口。
  • 新增、编辑、删除路由使用弹窗或行内表单,不单独跳转页面。
  • 删除 default 路由需要二次确认,因为它是 fallback 路由。
  • API 错误直接展示服务端返回的错误信息,便于运维判断问题。

用户与权限设计

第一版采用 SQLite 用户表和内存会话,不开放匿名管理 API。这里的“游客”是已登录用户的只读角色不是未登录访问。

角色权限:

角色 权限
管理员 admin 所有管理能力,包括用户管理、服务配置、路由管理、状态和指标查看
成员 member 查看状态、指标和服务状态;新增、修改、删除路由
游客 guest 只能登录、注销、查看自己的信息、查看当前 SQLite 路由

认证流程:

  • POST /admin/api/setup 只在用户表为空时可用,用于创建第一个管理员。
  • POST /admin/api/auth/login 使用用户名和密码登录。
  • 登录成功后服务端生成随机会话 token响应给前端。
  • 前端把会话 token 保存在 sessionStorage,后续 API 使用 Authorization: Bearer <session_token>
  • 服务端在内存中保存会话,超过会话有效期后失效;进程重启后所有会话失效。
  • 未登录返回 401,已登录但权限不足返回 403

用户规则:

  • 密码只保存哈希,建议使用 bcrypt 或 argon2id不保存明文密码。
  • 用户字段至少包含:usernamerolepassword_hashdisabledcreated_atupdated_at
  • 管理员不能删除或禁用最后一个可用管理员账号。
  • 修改用户、重置密码、禁用用户都要记录审计日志。

SQLite 存储设计

建议使用一个 SQLite 数据库保存所有后台状态。优先选择不依赖 CGO 的 SQLite driver降低交叉编译和容器部署成本。

基础表:

CREATE TABLE IF NOT EXISTS schema_migrations (
    version INTEGER PRIMARY KEY,
    applied_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS users (
    username TEXT PRIMARY KEY,
    role TEXT NOT NULL,
    password_hash TEXT NOT NULL,
    disabled INTEGER NOT NULL DEFAULT 0,
    created_at INTEGER NOT NULL,
    updated_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS routes (
    host TEXT PRIMARY KEY,
    upstream TEXT NOT NULL,
    enabled INTEGER NOT NULL DEFAULT 1,
    note TEXT NOT NULL DEFAULT '',
    created_at INTEGER NOT NULL,
    updated_at INTEGER NOT NULL,
    updated_by TEXT NOT NULL DEFAULT ''
);

CREATE TABLE IF NOT EXISTS services (
    name TEXT PRIMARY KEY,
    enabled INTEGER NOT NULL,
    port INTEGER NOT NULL DEFAULT 0,
    options_json TEXT NOT NULL DEFAULT '{}',
    restart_required INTEGER NOT NULL DEFAULT 0,
    created_at INTEGER NOT NULL,
    updated_at INTEGER NOT NULL,
    updated_by TEXT NOT NULL DEFAULT ''
);

CREATE TABLE IF NOT EXISTS audit_logs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    actor TEXT NOT NULL,
    source_ip TEXT NOT NULL,
    action TEXT NOT NULL,
    target_type TEXT NOT NULL,
    target_id TEXT NOT NULL,
    success INTEGER NOT NULL,
    message TEXT NOT NULL DEFAULT '',
    created_at INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_routes_enabled ON routes(enabled);
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at);

服务配置记录:

name 默认 enabled 默认 port 说明
tcp_admin 1 25565 Minecraft TCP 和 Admin 共用入口
kcp 0 25566 KCP 服务
quic 0 25565 QUIC 服务,具体协议参数放在 options_json
websocket 0 25566 WebSocket 服务path 放在 options_json

注意:

  • tcp_admin 是基础入口,不允许在后台禁用。
  • tcp_admin.port 修改后第一版按重启后生效处理。
  • KCP、QUIC、WebSocket 配置修改后,如果当前实现无法安全热更新,则标记 restart_required = 1 并在页面提示。
  • 后续可以把服务运行状态放在内存中,不需要写入 SQLite。

SQLite 运行参数:

  • 启动后设置 PRAGMA journal_mode=WAL
  • 设置合理的 busy_timeout,避免后台写入和读取短暂冲突直接失败。
  • 所有写操作使用事务。
  • 路由和服务配置写入成功后,刷新对应的内存快照。

路由存储与运行时读取

路由管理以 SQLite 为唯一持久化来源。

运行时读取:

  • 启动时从 SQLite 加载所有 enabled = 1 的路由,发布为内存只读快照。
  • mapToHost 从路由快照读取 host 到 upstream 的映射,不再读取 config.Hosts
  • 路由 API 写入 SQLite 成功后,重新加载路由快照;不需要 reload。
  • 如果快照中没有目标 host则尝试 default;没有 default 时按现有未命中逻辑关闭连接并记录日志。

写入流程:

  1. 校验 host、upstream、enabled 和 note。
  2. 在 SQLite 事务中执行 insert、update、delete 或 enable/disable。
  3. 事务提交成功后重新加载路由快照。
  4. 快照发布成功后返回 API 成功。
  5. 如果快照发布失败,返回错误并保留 SQLite 已提交数据;旧路由快照继续服务已有逻辑。

并发与一致性:

  • 后台写操作串行执行,避免并发修改同一 host 时互相覆盖。
  • 路由快照使用 copy-on-write 发布,转发热路径只做只读 map 查询。
  • 没有 config.toml 热加载,也不存在配置文件覆盖后台路由的问题。

路由校验规则

host 校验:

  • 不能为空。
  • 允许 default
  • 不允许包含空白字符。
  • 不允许包含 /,避免和 URL 路由混淆。

upstream 校验:

  • 不能为空。
  • 支持现有前缀:无前缀 TCP、kcp://quic://haproxy://
  • 去掉协议前缀后,必须能解析为 host:port
  • 不在第一版新增 websocket upstream 前缀,因为当前 README 未列出该前缀的实际实现。

指标设计

第一版只做轻量统计,避免影响 TCP 转发热路径。

建议新增全局 gatewayMetrics,内部使用 sync/atomic

  • total_connections
  • active_connections
  • tcp_connections
  • websocket_connections
  • route_hits{host}
  • route_misses
  • upstream_dial_errors

实现原则:

  • 只在连接进入、退出、路由成功或失败时更新计数。
  • 不在每次 copyForward 读写时计数。
  • route 维度只记录 host 计数,不记录完整客户端 IP。

测试计划

单元测试:

  • 无配置文件时默认创建 SQLite 并写入默认服务配置。
  • 默认启动配置包含 tcp_admin enabled 和端口 25565
  • Admin path、api_path 默认分别为 /admin//admin/api
  • MC_GATEWAY_TCP_ADMIN_PORTMC_GATEWAY_ADMIN_PATHMC_GATEWAY_ADMIN_API_PREFIX 能覆盖启动期入口配置。
  • 启动期环境变量非法时返回明确错误,不创建含错误值的默认服务配置。
  • 首次初始化管理员、重复初始化拒绝、管理员登录和禁用用户拒绝登录。
  • 登录、注销、会话过期。
  • 权限 middleware 的 200、401、403 场景。
  • 管理员用户 API 的新增、改角色、重置密码、禁用和删除。
  • 不能删除或禁用最后一个可用管理员。
  • 路由 API 的新增、修改、删除和校验失败。
  • SQLite schema migration、空库初始化、WAL 和 busy timeout 配置。
  • 路由写入 SQLite 后能刷新内存路由快照。
  • 服务配置 API 能更新 KCP、QUIC、WebSocket 配置并标记是否需要重启。
  • 游客可以读取 routes但不能写 routes、配置 services、读取 metrics 或 users。

集成测试:

  • 不提供 config.toml 时,进程默认通过 25565 提供 TCP/Admin 入口。
  • 设置 MC_GATEWAY_TCP_ADMIN_PORT 和 Admin path/API prefix 后,进程通过指定端口和路径提供后台入口。
  • 设置自定义 Admin API prefix 后,登录、状态、路由等 API 都挂载到新的 prefix 下。
  • 用户表为空时可访问初始化页面并创建第一个管理员。
  • 管理员或成员登录后可访问 /admin/api/status
  • 游客账号登录后可以通过 25565 读取 /admin/api/routes
  • 成员通过后台新增或修改路由后,新的 Minecraft host 立即按 SQLite 记录转发。
  • 同一端口下 Minecraft 握手仍然进入 TCP 分支,并能完成 host 路由。
  • WebSocket 启用后,按后台配置的端口和 path 提供服务。

性能验证:

  • 管理页面变更后继续运行现有 TCP 转发 benchmark。
  • 重点确认路由快照查询和指标计数没有进入已建立连接后的 copyForward 热路径。

建议命令:

go test ./...
go test -race ./...
go test -run TestTcpWebPortReuse ./cmd/gateway
go test -bench 'Benchmark.*tcp' ./cmd/gateway

实施步骤

  1. 移除 config.toml 启动依赖,改为无配置默认值启动。
  2. 新增 SQLite 打开、schema migration、WAL 和默认服务配置初始化。
  3. 增加启动期环境变量解析和校验,覆盖 TCP/Admin 端口、Admin 页面路径和 Admin API 前缀。
  4. 将 TCP/Admin 共享 listener 固定为基础入口,默认端口 25565,并支持启动期端口覆盖。
  5. 把 WebSocket handler 创建逻辑扩展为统一 newGatewayHTTPHandler()
  6. 增加用户表、首次初始化管理员、密码哈希和会话管理。
  7. 增加 Admin 静态页面、初始化页面、登录页面和权限 middleware。
  8. 增加 auth、me、users、status、routes、services、metrics、audit-logs API。
  9. mapToHost 改为读取 SQLite 路由快照,不再读取 config.Hosts
  10. 实现 routes 写入 SQLite、事务提交和路由快照刷新。
  11. 实现 services 写入 SQLite并接入 KCP、QUIC、WebSocket 启动配置。
  12. 实现 users 写入 SQLite 和权限校验。
  13. 移除或废弃配置文件 watcher 和基于文件的 reload 逻辑。
  14. 补充测试和 benchmark 验证。

验收标准

  • 没有 config.tomlgateway 默认在 25565 启动 TCP 转发和后台管理页面。
  • http://<host>:25565/admin/ 可访问初始化或登录页面。
  • 设置 MC_GATEWAY_TCP_ADMIN_PORTMC_GATEWAY_ADMIN_PATHMC_GATEWAY_ADMIN_API_PREFIX 后,后台入口使用环境变量指定的端口和路径。
  • 同一端口下 Minecraft 客户端连接和 host 路由行为正常。
  • 用户表为空时只能创建第一个管理员;创建后初始化接口不可再次使用。
  • 未登录的管理 API 请求返回 401,已登录但权限不足返回 403
  • 管理员可以新增用户、禁用用户、修改角色和重置密码。
  • 管理员可以配置 KCP、QUIC、WebSocket 服务。
  • 成员可以查看、搜索、新增、修改、删除 SQLite 路由记录,变更后立即影响新连接路由。
  • 游客可以查看和搜索当前 SQLite 路由记录,但看不到写操作入口,直接调用写 API 返回 403
  • 删除或修改旧 config.toml 不影响后台管理中的用户、服务配置和路由。
  • SQLite 写入失败或路由快照刷新失败时,页面展示明确错误,旧路由快照仍可继续工作。
  • go test ./...go test -race ./... 和 TCP benchmark 完成后无新增失败。