20 KiB
20 KiB
管理页面设计
背景
当前 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.js或js/。MC_GATEWAY_ADMIN_STATIC_DIR指向 Admin 前端构建产物目录;Docker 镜像内使用/usr/share/mc-gateway/admin_static。- 环境变量覆盖的是本次进程的 Admin 入口;第一次创建 SQLite 默认服务配置时,应把解析后的 TCP/Admin 端口写入
services.tcp_admin.port。
启动流程:
- 读取并校验启动期环境变量,得到 SQLite 路径、TCP/Admin 端口、Admin 页面路径、Admin API 前缀。
- 打开 SQLite 数据库;不存在时自动创建。
- 执行 schema migration。
- 确保默认服务配置存在:TCP/Admin listener 启用,端口为启动期解析后的端口。
- 如果用户表为空,进入首次初始化模式。
- 加载启用的路由记录为内存只读快照。
- 启动 TCP/Admin 共享 listener。
- 按 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 由后台管理配置启用状态和端口。
- 如果某个服务的端口或协议参数无法热更新,页面必须标记为“重启后生效”或提供明确的重启服务操作。
- 路由变更不需要重启,也不需要 reload;SQLite 写入成功后刷新路由快照即可影响新连接。
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,不保存明文密码。
- 用户字段至少包含:
username、role、password_hash、disabled、created_at、updated_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时按现有未命中逻辑关闭连接并记录日志。
写入流程:
- 校验 host、upstream、enabled 和 note。
- 在 SQLite 事务中执行 insert、update、delete 或 enable/disable。
- 事务提交成功后重新加载路由快照。
- 快照发布成功后返回 API 成功。
- 如果快照发布失败,返回错误并保留 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_connectionsactive_connectionstcp_connectionswebsocket_connectionsroute_hits{host}route_missesupstream_dial_errors
实现原则:
- 只在连接进入、退出、路由成功或失败时更新计数。
- 不在每次
copyForward读写时计数。 - route 维度只记录 host 计数,不记录完整客户端 IP。
测试计划
单元测试:
- 无配置文件时默认创建 SQLite 并写入默认服务配置。
- 默认启动配置包含
tcp_adminenabled 和端口25565。 - Admin path、api_path 默认分别为
/admin/和/admin/api。 MC_GATEWAY_TCP_ADMIN_PORT、MC_GATEWAY_ADMIN_PATH、MC_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
实施步骤
- 移除
config.toml启动依赖,改为无配置默认值启动。 - 新增 SQLite 打开、schema migration、WAL 和默认服务配置初始化。
- 增加启动期环境变量解析和校验,覆盖 TCP/Admin 端口、Admin 页面路径和 Admin API 前缀。
- 将 TCP/Admin 共享 listener 固定为基础入口,默认端口
25565,并支持启动期端口覆盖。 - 把 WebSocket handler 创建逻辑扩展为统一
newGatewayHTTPHandler()。 - 增加用户表、首次初始化管理员、密码哈希和会话管理。
- 增加 Admin 静态页面、初始化页面、登录页面和权限 middleware。
- 增加 auth、me、users、status、routes、services、metrics、audit-logs API。
- 将
mapToHost改为读取 SQLite 路由快照,不再读取config.Hosts。 - 实现 routes 写入 SQLite、事务提交和路由快照刷新。
- 实现 services 写入 SQLite,并接入 KCP、QUIC、WebSocket 启动配置。
- 实现 users 写入 SQLite 和权限校验。
- 移除或废弃配置文件 watcher 和基于文件的 reload 逻辑。
- 补充测试和 benchmark 验证。
验收标准
- 没有
config.toml时,gateway 默认在25565启动 TCP 转发和后台管理页面。 http://<host>:25565/admin/可访问初始化或登录页面。- 设置
MC_GATEWAY_TCP_ADMIN_PORT、MC_GATEWAY_ADMIN_PATH、MC_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 完成后无新增失败。