4.3 控制台:任务管理面板
场景:命令行的尽头
平台运行两个月之后,任务管理的操作还停留在命令行:建任务要登 master 敲 SQL,看进度要查表,暂停任务要改状态字段。开发自己忍忍也就罢了,但凡有别人想看一眼数据,第一句话永远是谁能帮我查一下。
转折点是一次事故。某个周日一个站点封禁升级,值班同事不得不一边处理封禁一边手动改任务状态,命令行敲错了一行 UPDATE 语句的 WHERE 条件,把一批任务的 worker 字段清空了。恢复花了两个小时。那天之后我们下定决心:所有运维操作必须走界面,命令行只保留只读查询。做出这个决定时开发内部其实有过犹豫——明明敲一行 SQL 十秒钟的事,做页面要两天。后来事实证明这两天是整个项目最划算的两天,因为界面化之后所有操作天然带上了审计与权限属性,这是命令行永远给不了的东西。
前一晚为了确认范围,我们三个开发对着一台机器的终端逐条核对,每个人都憋着一股劲:这是最后一次靠命令行救火。从这个意义上说,控制台不是功能需求,是事故的产物,它身上每个设计都能追溯到某次具体的疼。
控制台的定位由此明确:它不是炫技项目,是安全护栏。每一个危险操作都要有确认、有审计、有权限控制。这一定位决定了后面的所有功能取舍:宁可界面丑一点、慢一点,也要让操作可追溯、可回滚。
方案:页面与权限的最小集合
控制台的功能我们刻意压到一个最小集合,不增不减:
| 页面 | 看什么 | 能操作什么 |
|---|---|---|
| 任务列表 | 状态、进度、所属 worker | 新建、暂停、恢复 |
| 站点管理 | 限速配置、健康度 | 改限速、禁用 |
| 结果检索 | 清洗结果明细 | 导出 CSV、查看快照 |
| 日志查询 | 最近采集日志 | 按级别过滤 |
| 操作审计 | 谁做了什么 | 只读 |
五个页面看起来寒酸,尤其跟同期那些大厂内部系统相比。但页面数量从来不是控制台的价值标准,覆盖高频操作才是。多做的页面没人用,反而稀释维护精力。我们的判断标准很简单:一个操作如果一周出现不到一次,就不配拥有页面,它可以是命令行、可以是脚本,等频率涨上来再说。这条过滤规则帮我们挡住了至少五个锦上添花的需求,节省的维护时间远超开发一个页面。
五个页面覆盖日常九成操作。审计页是唯一纯只读页面,也是我们认为最不能省的一个:没有它,前四个页面的操作就是黑箱。
权限分三档:只读(观察数据)、操作员(管理任务与站点)、管理员(改限速基线、管密钥)。绝大多数人给只读,操作员只有值班排班的几个人,管理员两个。权限升降一律走审批流,不接受口头通知。最小权限原则在小团队里容易被嫌弃麻烦,但等到某次误操作发生时它会救你。任何人被升权限时团队群里会自动广播一条通知,这条机制比制度文档管用——阳光下操作,滥权自然无处藏身。
技术形态上控制台是一个纯前端单页应用加一组后端 API。前端打包成静态文件放在 COS,后端 API 挂在 master 服务上。这个部署结构与 4.4 节的 CDN 加速是配套的。部署结构定了之后还有一件小事值得坚持:前端构建产物必须可回滚。每次发版保留构建包与对应版本号,一旦新版前端出问题能一分钟退回,不用等服务端配合。
关于前端框架还有一句经验之谈:我们是后端团队,选了最熟悉的方案而不是最流行的。社区里前端框架三年一换,但控制台的生命周期远长于这个周期。选一个团队大概率修得动的技术栈,比选一个酷的更重要。事实证明两年多过去,控制台只做过一次大版本升级,选型保守在这里完全没有成为负担。
配置与代码:危险操作的统一处理
所有变更类 API 走同一个包装器,统一做鉴权、审计、二次确认三件事:
func (s *Server) mutate(role string, action string,
fn func(ctx context.Context) error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !hasRole(r, role) {
http.Error(w, "forbidden", 403)
return
}
confirm := r.Header.Get("X-Confirm")
if confirm != r.URL.Path {
http.Error(w, "missing confirm", 428)
return
}
if err := fn(r.Context()); err != nil {
http.Error(w, err.Error(), 500)
return
}
s.audit(r, action)
w.Write([]byte("ok"))
}
}
二次确认用的不是再弹一次框那种客户端确认,而是要求请求头带 X-Confirm 等于操作路径——意思是调用方必须明确知道自己要动的是哪个资源。这个设计挡住了前端页面上手滑的误点,也挡住了脚本里忘记带参数的批量调用。
再补充一个控制台交互的教训:危险操作的背后不是二次确认,而是可撤销的设计。确认框点习惯了等于没有确认。真正救场的功能是任务暂停之后还能恢复、删除是软删除留七天观察期。控制台把每一个危险按钮都做成可回滚动作之后,误操作的处理从恢复两小时变成点一下撤销。
审计记录写得尽量朴素:操作人、动作、目标、时间、来源 IP。不记录复杂 diff,diff 留给日志系统。审计页面的价值在于出事之后的第一问——谁动过——能在三秒内回答。
任务详情页是整个控制台里信息密度最高的一页,我们迭代了三次才稳定。第一版堆了十二个区块,打开像看仪表盘,人人喊看不懂。第二版砍到六个区块,又有人说关键信息要点两次才能看到。第三版定了原则:详情页只回答一个问题——这个任务现在好不好。好的任务只显示状态摘要,出问题的任务才展开错误堆栈与最近告警。信息分层不是一屏塞多少,而是让视线第一眼落在唯一重要的那个数字上。
任务列表页的数据聚合也是个细节。直接查 MySQL 的任务表再 join worker 心跳会很慢,我们让 master 每十秒把全量任务快照推到 Redis,列表页读 Redis。这个快照设计的副作用是任务列表的绝对实时性差了几秒,值班同事最初有意见,后来习惯了——因为列表永远稳定不会卡顿,而真正的紧急告警本来就走 5.1 节的告警通道而不是靠人刷页面。任何列表页的刷新频率超过数据真实变化频率时,中间就该有一层快照缓存。把监控与人工巡览分开,是控制台设计里一个原则性的取舍。把监控与人工巡览分开,是控制台设计里一个原则性的取舍。
踩坑:控制台的四个坑
第一个坑是审计页漏掉了批量操作。最初审计只记录单条变更,批量暂停二十个任务只留一条记录。出了问题查不到具体影响范围。修正为批量操作展开成多条审计记录,每条对应一个目标资源。
第二个坑是前端缓存旧配置。限速参数改了,页面显示的还是旧值。原因是接口返回带强缓存头。修正:查询类加短缓存(五秒),变更类一律 no-store。缓存策略绝不能靠默认值,必须逐类显式声明。
第三个坑是控制台自身的可用性。master 重启时控制台也跟着不可用,偏偏这时候最需要看任务状态。修正:静态资源全部放 COS,控制台与 API 后端保持可独立访问,至少只读页面在 API 抖动时还能打开看数。
第四个坑是密码管理。早期大家各凭习惯,密码强度参差。后来统一走签名密钥加 IP 白名单,高危操作要二次输入独立操作口令。密钥定期轮换清单在 5.4 节。
小结
控制台做好之后,团队的值班体验完全变了:新人第一次值班就能独立处置常规任务,不需要老员工在旁边当人肉手册。安全与效率在控制台这里不是选择题,而是同一件事的两面——好的界面约束让正确操作成为最顺手的操作。功能最小集合、权限三档、审计前置、危险操作二次确认,这四样做齐,控制台就不是面子工程。还有一个意外收获:控制台成了新人入职能独立操作的第一个系统,培训成本从一周缩到一天,因为它把平台的全部概念都翻译成了人能看懂的页面。下一节讲控制台的静态资源与实践指南页面怎么通过 CDN 提速,继续看 4.4。
控制台还有一个低调但关键的设计:只读镜像页。主 API 有任何故障时,值班同事打开的是一个纯静态的历史快照页,展示上一次成功的数据视图并明确标注时间戳。它没有交互能力,但它永远不会打不开。在最慌的时刻给值班同志一个能看的页面,比满屏错误提示强一百倍。韧性设计的尽头,是承认某些时刻能看就好。镜像页每天自动生成一次,成本几乎为零,给它排上号就不算过度设计。镜像页之外我们还养成了一个习惯:控制台每个危险操作按钮旁边都留着一行灰字,写清这个操作影响的任务数与最近一次执行时间。点下去之前让操作者多看到一点上下文,误操作率肉眼可见地降了下来。界面设计的善意,往往就是一行说明文字的事。
