ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Status Deck:Golang+SQLite+WebSocket构建开发者实时状态仪表盘

Status Deck:Golang+SQLite+WebSocket构建开发者实时状态仪表盘 每天早上到工位我做的第一件事其实特别没技术含量打开浏览器挨个点开服务器磁盘占用页面、仓库分支状态、测试环境地址、CI任务面板把每个页面扫一眼确认今天一切正常。这个动作我持续了大半年直到某天实在觉得烦决定写一个属于自己的桌面仪表盘把分散在各处的状态浓缩到一个页面里。这就是这次要分享的Status Deck。它可以理解为一个“给开发者的状态看板”把自己日常要盯的那些系统和任务信息集中起来用实时刷新的方式铺在一个网页上。你不需要再去记忆各种后台地址不需要反复登录不同的运维系统打开一个页面就能看到CPU、内存、磁盘、服务端口、仓库分支状态等等。如果后续继续迭代还能接上自定义任务提醒、构建流水线状态甚至让AI帮你判断异常。这篇文章是系列第一篇目标是做一个能用的MVP版本Golang做后端采集和推送SQLite做本地存储WebSocket把数据实时推给前端页面。文章不会搞大而全的架构只讲从一个空白目录开始怎么把一个真正的仪表盘项目拉通以及我实际开发过程中遇到的那些坑。如果你是正在考虑“前端转全栈”或者想自己动手写点全栈成品的人这篇会比较合适技术栈干净每一行都能看懂也能直接跑起来。1. 为什么程序员需要一个自己做的仪表盘Status Deck的由来1.1 每天早上最烦人的五件事先还原一下我真实的早晨工作流。我手上有一台跑测试环境的服务器一个公司GitLab仓库组还有几个定时脚本和一堆后台管理页面。正常情况下我每天要确认的事情包括服务器磁盘空间还剩多少日志有没有把硬盘写满测试环境的几个服务端口是不是活着有没有半夜崩掉当前负责的几个Git仓库有没有未提交的改动或者别人的分支有没有推到一半卡住昨晚的定时任务到底跑没跑导出的数据文件是否生成今天要看的几个面板地址每个都要点进去再退出非常浪费时间。这些事情单独看都不复杂但叠在一起就很消耗注意力。更尴尬的是很多时候问题不是“有没有问题”而是“我没注意到出了问题”——硬盘在某个下午悄悄写满了CI任务卡了一晚上仓库里有个未提交的文件差点被推上去。你没法每时每刻都记得去检查这些分散的地方所以需要有一个东西替你盯着把状态主动送到你眼前。这就是Status Deck最原始的动机。它不是那种面向整个团队的运维监控平台也不是要取代Prometheus、Grafana之类的专业系统就是一个跑在自己机器上的小工具把和“我”相关的开发状态汇总起来。说白了它是给我自己看的东西所以我希望它足够轻、足够快还能完全按我的习惯来安排布局。1.2 0.1版本的核心需求清单第一版我不打算做花哨的东西先把最必需的需求定下来。我的原则是能在一秒内扫完的状态统统放进来需要二次点击才能看到的信息暂缓。下面是我列出的核心需求清单系统资源监控CPU使用率、内存占用、磁盘剩余空间至少每5秒刷新一次服务探活探测指定服务器的若干端口是否可连接状态要直观在线/离线Git仓库状态对指定目录执行git status判断工作区是否干净、分支名是什么、有没有待推送提交本地历史记录最近一段时间的状态指标写入本地数据库方便回看趋势Web页面展示一个网页把所有信息分区域展示通过WebSocket实现实时推送不用手动刷新快捷入口把常用后台地址和管理链接做成可点击的卡片省去翻书签的麻烦。与之对应的优先级可以用下面这张表来表达功能模块优先级说明系统资源监控P0当天就要能看采集间隔可控服务端口探活P0本地和测试环境都要支持Git仓库状态P0只做状态展示暂不做提交操作SQLite历史存储P1写入频率低保留最近24小时WebSocket实时推送P0前端更新体验的核心快捷入口卡片P1纯前端配置不需要后端参与AI异常摘要P2放在后续版本先不碰当时我把P0压缩得很厉害就是为了确保第一版能在两三天内跑起来。项目正文里没提到太多细节我自己动手时也是这个节奏先把最核心的链路走通再考虑漂亮和丰富。1.3 坚决不做的事什么内容不进第一版很多人做自用工具容易失控做着做着就想加权限系统、告警规则、多用户、图表动画最后变成一个半吊子的重型平台。我在0.1阶段明确画了一条线下面这些东西一律不做不做用户登录和权限管理。自用工具跑在内网或者本机没有多用户需求不做复杂的图表库和动画效果。实时数字和状态灯比炫酷图形更实用不做告警通知。第一版只负责“呈现状态”判断和提醒后续再说不做Docker部署和集群化。单个二进制能跑就是胜利。这些克制在后来帮了大忙。整个MVP没有引入任何重依赖前端HTML文件加后端Go程序两个东西就能跑起来。守住边界才能快速得到一个真正每天都会打开的工具。2. 技术选型Golang SQLite WebSocket这套组合到底怎么说服自己2.1 摆在我面前的四套方案动手之前我认真比较了几套常见组合。这里把思考过程写出来方便你以后做类似自用项目时参考。当时考虑的方案有Go SQLite WebSocket 原生HTML一个编译产物搞定所有事情Node.js Express WebSocket Vue生态熟悉前端写起来快但需要Node环境常驻Python FastAPI SQLite开发效率高但部署时需要带Python环境或者打包成复杂产物Grafana Prometheus Node Exporter免费臃肿专业但过重我还得再维护两套服务。每一套都有各自的价值但我需要的是“我能完全掌控、可快速迭代、资源占用低”的方案。下面是当时做的横向对比方案优点缺点是否合适Go SQLite WebSocket编译后单文件、跨平台、并发模型简单前端需要自己写开发速度略慢非常合适Node.js Express Vue全栈同语言、生态丰富常驻进程相对吃内存、依赖较多一般Python FastAPI上手快、代码量少部署环境麻烦一般Grafana Prometheus功能强、开箱即用配置复杂、资源占用高、难以按需定制不适合自用小工具2.2 为什么是Golang而不是Node.js或Python选择Golang最关键的原因是它编译出来就是一个独立的二进制文件扔到哪台机器上都能跑不需要目标机器预装Node或者Python环境。对我这种“服务器上能少装一个东西就少装一个东西”的人来说这一点非常加分。另一个原因是Golang的并发模型。Status Deck后面需要同时跑多个采集循环一个定时采集CPU内存和磁盘一个定时探测端口一个定时跑git status。如果我用多线程去写这些事情也不是不行但Golang的goroutine会让代码结构自然很多。每个采集器就是一个独立的循环互不干扰后续想加新的采集源也只需要再启动一个goroutine。我当时还考虑了开发效率问题。虽然Golang写前端页面确实没有Vue那么爽但这个项目的后端逻辑并不复杂核心就是对指标数据的采集、存储和转发。Golang标准库里的上下文、定时器、HTTP处理都很好用代码量不会比Python多太多。再加上gopsutil这个库可以直接拿到系统层面的CPU、内存、磁盘数据跨平台能力也很稳。2.3 为什么是SQLite而不是PostgreSQL按理说做一个仪表盘项目数据量小得可怜用不用数据库都无所谓。但我还是决定把历史指标存下来因为后续版本想做趋势图和异常分析没有历史数据什么都做不了。在数据库选型上我直接排除了MySQL和PostgreSQL。原因很简单Status Deck是单机工具为了存几个指标再跑一个数据库服务代价完全不匹配。SQLite是一个内嵌式数据库数据就是一个文件不需要额外进程读写速度还很快。对“一个人用、每几秒写一次”的场景它绰绰有余。用SQLite有一个地方要特别注意写入并发控制。SQLite同一时间只允许一个写事务如果你的采集器多个goroutine同时往里面写容易碰到“database is locked”的报错。这个问题我在后面填坑章节会展开说这里先记住结论。实际使用中我会开WAL模式让读写并发友好一些同时把写入操作收敛到一个单独的goroutine里处理。2.4 为什么是WebSocket而不是普通轮询做实时刷新有很多种办法最简单的是前端每隔几秒用fetch拉一次数据。这种方案写起来确实快但在Status Deck的场景下不太优雅每一次拉取都要建立新的HTTP连接服务端要重复计算同一批指标前端还要处理请求失败、超时等一堆边界情况。WebSocket的思路是建立一条长连接服务端主动把数据推给前端。数据一变化浏览器立刻收到不用反复轮询延迟也低。我用工具实测过从采集器拿到数据到浏览器页面渲染出来整个链路大概是200毫秒以内这个实时性体验是轮询很难做到的。用WebSocket还有一个附带的好处以后如果要做多端同步比如手机上看状态客户端只需要订阅同一个WebSocket服务服务端不需要为每个平台开发不同的推送接口。这个设计在系列第二篇做uniapp多端版本时会很有价值。3. 数据怎么流转从采集器到浏览器只用了200毫秒的链路设计3.1 三个采集器各管一摊Status Deck的整个数据流其实不复杂采集器负责取数存储层负责落盘WebSocket负责推送前端负责渲染。我把它拆成几个独立模块后整体架构变得非常清晰。第一类采集器是系统资源采集器。它通过gopsutil库读取CPU使用率、内存占用、磁盘剩余空间还有进程数、运行时间等信息。采集周期我设为5秒这个频率足够灵敏又不会给系统带来明显压力。数据拿到后一方面推给WebSocket广播另一方面写入SQLite。第二类采集器是服务探活采集器。这个更简单本质就是一个TCP连接测试对指定的主机和端口发起连接能连上就标记为online连不上就标记为offline。我会配置一个列表里面写上测试环境的IP和端口采集器每隔10秒跑一遍。第三类采集器是Git仓库状态采集器。它定时进到指定仓库目录执行git status --porcelain和git branch --show-current命令解析输出之后判断工作区是否干净、当前在哪个分支。这个模块是纯开发者的需求通用监控平台通常不会管你的仓库干不干净。3.2 SQLite表结构与时间窗口数据库设计我保持了极简风格整体就两张核心表。第一张叫metric_records用来存系统资源指标结构大概是这样的id自增主键collected_at采集时间戳metric_type指标类型比如cpu、memory、diskmetric_value数值extraJSON格式的附加字段可以存一些额外信息。第二张表叫service_status用来存服务探活结果id自增主键checked_at探测时间service_name服务名称host主机地址port端口号statusonline或offline。写入策略上我不是每条数据都立刻直接写库。先通过一个channel把待写入的数据丢给数据库写入goroutine由这个goroutine统一处理。这样能够避免多个采集器同时操作SQLite导致的锁冲突也方便以后做批量写入。保留策略方面我设定只保留24小时的数据。之所以不保留更长时间是因为本地的工具没有那么大存储需求时间长了文件也会膨胀。这个策略在写查询接口时带来一个天然的好处只需要按时间窗口过滤数据前端就能快速拿到最近24小时的趋势数据不会越查越慢。3.3 WebSocket Hub如何转发WebSocket部分我参考了很多项目里常用的“Hub模式”。简单来说就是维护一个在线客户端的集合谁连上来就注册断开了就注销当有数据产生时遍历所有在线客户端并广播出去。要让这个模式真正好用关键在于“注册、注销、广播”这三个操作不能相互干扰。Golang里最自然的做法是拆成三个channelregister、unregister、broadcast后台跑一个独立的goroutine去处理它们。这样无论客户端何时连接断开Hub都不会出竞争问题。在设计广播的数据结构时我定义了一个统一的消息格式包含type和payload。type用来告诉前端这次推送的数据属于什么类别比如metrics、service、git。payload则是真正的数据内容。这个设计让前端可以根据消息类型去更新不同的区域不会一锅粥地全量渲染。其实前端拿到数据后不需要做太复杂的处理。比如收到的消息是metrics就去更新CPU数字、内存条进度和磁盘进度收到service消息就改变对应服务卡片的颜色收到git消息就刷新仓库状态列表。整个逻辑是事件驱动的非常直接。3.4 前端布局先画二维网格前端页面我一开始就想好要做成Grid布局。既然叫Status Deck本质就是一张铺开的状态卡片板每个卡片对应一种数据。我用CSS Grid把首屏分成了几个区域顶部状态栏显示Status Deck运行状态、当前时间、最近一次更新延迟系统资源卡片阵列CPU使用率、内存使用量、磁盘剩余空间各占一个格子服务探活列表每个服务一张小卡片用绿色和红色区分在线离线Git仓库状态区列出各仓库的分支名、是否干净、是否有待推送提交快捷入口区放几个常用链接点击跳转。这样的布局在电脑屏幕上看起来非常直观。24英寸显示器上一屏就能放下所有核心信息我甚至不需要滚动鼠标滚轮。4. 从空白目录到拉通首屏MVP落地全过程4.1 工程初始化与依赖清单现在进入实际操作。如果你打算跟着做先把Go环境准备好版本1.21以上就行。我在终端里建好项目目录然后执行命令初始化mkdir statusdeck cd statusdeck go mod init statusdeck接下来引入需要的依赖。我这边用到的主要是这几个go get github.com/gorilla/websocket go get github.com/mattn/go-sqlite3 go get github.com/shirou/gopsutil/v4/cpu go get github.com/shirou/gopsutil/v4/mem go get github.com/shirou/gopsutil/v4/disk如果你不想用第三方的sqlite驱动也可以考虑用modernc.org/sqlite这是一个纯Go实现的驱动编译时不需要CGO交叉编译更省心。我第一版为了方便直接用mattn/go-sqlite3后续如果要做跨平台编译再切换也不难。项目目录结构我这样划分main.go程序入口负责启动采集器、初始化数据库、启动WebSocket服务hub.goWebSocket连接管理和消息广播collectors/采集器模块系统指标、服务探活、Git状态分别独立store/SQLite存储与查询web/前端静态文件。这样拆分的理由是后续加功能比较容易。比如要给采集器增加新的指标只要在collectors目录里加一个文件main.go里启动对应goroutine即可其他模块不用动。4.2 核心采集器实现系统资源采集器是第一个要写的模块。gopsutil库已经把底层API都封好了使用起来很直接。下面是一个典型的实现片段package collectors import ( context time github.com/shirou/gopsutil/v4/cpu github.com/shirou/gopsutil/v4/disk github.com/shirou/gopsutil/v4/mem ) type MetricsSnapshot struct { Timestamp time.Time json:timestamp CPUPct float64 json:cpu_pct MemUsed uint64 json:mem_used MemTotal uint64 json:mem_total DiskUsed uint64 json:disk_used DiskTotal uint64 json:disk_total } func CollectMetrics() (*MetricsSnapshot, error) { ctx : context.Background() snap : MetricsSnapshot{ Timestamp: time.Now(), } pct, err : cpu.PercentWithContext(ctx, 0, false) if err ! nil { return nil, err } if len(pct) 0 { snap.CPUPct pct[0] } vm, err : mem.VirtualMemoryWithContext(ctx) if err ! nil { return nil, err } snap.MemUsed vm.Used snap.MemTotal vm.Total du, err : disk.UsageWithContext(ctx, /) if err ! nil { return nil, err } snap.DiskUsed du.Used snap.DiskTotal du.Total return snap, nil }这段代码的思路很清晰分别采集CPU、内存、磁盘数据打包成一个快照结构返回。CPU百分比我传了0作为interval参数表示取当前瞬时值不等待采样周期。磁盘路径默认取了根目录/如果你要监控其他挂载点改成对应的路径即可。服务探活采集器代码更短核心就是一个TCP超时连接测试func CheckPort(host string, port int, timeout time.Duration) bool { addr : fmt.Sprintf(%s:%d, host, port) conn, err : net.DialTimeout(tcp, addr, timeout) if err ! nil { return false } conn.Close() return true }注意这里一定要设置超时时间。如果目标服务处于半死状态TCP连接可能长时间不返回探活逻辑会被卡住。我设的是3秒超时既不会误报也不会让采集循环越积越多。Git仓库状态采集用的则是os/exec。因为git仓库目录可能和程序运行目录不在一起所以每次执行命令前要设置cmd.Dirfunc GetGitStatus(repoPath string) (GitStatus, error) { branchCmd : exec.Command(git, branch, --show-current) branchCmd.Dir repoPath branch, err : branchCmd.Output() if err ! nil { return GitStatus{}, err } statusCmd : exec.Command(git, status, --porcelain) statusCmd.Dir repoPath output, err : statusCmd.Output() if err ! nil { return GitStatus{}, err } dirty : len(strings.TrimSpace(string(output))) 0 return GitStatus{ Branch: strings.TrimSpace(string(branch)), Dirty: dirty, }, nil }这个采集器我设置了30秒运行一次。因为git status本身要读取仓库文件状态跑太频繁没必要还可能会在高负载项目里带来IO抖动。对于“看看工作区干不干净”这个需求30秒绰绰有余。4.3 WebSocket推送实现WebSocket Hub是我整个后端最核心的部分。用channel加后台goroutine的方式可以避免锁竞争代码如下package main import ( sync github.com/gorilla/websocket ) type Hub struct { clients map[*websocket.Conn]bool register chan *websocket.Conn unregister chan *websocket.Conn broadcast chan []byte mu sync.Mutex } func NewHub() *Hub { return Hub{ clients: make(map[*websocket.Conn]bool), register: make(chan *websocket.Conn), unregister: make(chan *websocket.Conn), broadcast: make(chan []byte, 256), } } func (h *Hub) Run() { for { select { case conn : -h.register: h.mu.Lock() h.clients[conn] true h.mu.Unlock() case conn : -h.unregister: h.mu.Lock() if _, ok : h.clients[conn]; ok { delete(h.clients, conn) conn.Close() } h.mu.Unlock() case msg : -h.broadcast: h.mu.Lock() for conn : range h.clients { err : conn.WriteMessage(websocket.TextMessage, msg) if err ! nil { conn.Close() delete(h.clients, conn) } } h.mu.Unlock() } } }这里给broadcast channel设置了缓冲256避免采集器推送过快时阻塞采集循环。实际使用中5秒一次推送根本到不了这个水位但预留缓冲让整体更稳健。在main.go里每个WebSocket连接建立后要做两件事注册到Hub同时启动一个读goroutine。读循环的作用不仅仅是接收客户端消息更重要的是用来检测连接是否断开。如果客户端异常掉线读操作会返回错误这时再执行unregister确保连接彻底释放。推送数据的入口很简单采集器拿到数据后序列化成JSON丢给Hub.broadcast就行data, _ : json.Marshal(snapshot) hub.broadcast - data4.4 前端页面没有框架也能做实时刷新第一版前端我不想引入Vue或React因为只有几个分区卡片原生JavaScript足够应付。页面结构大致是这样!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleStatus Deck/title style body { font-family: system-ui, sans-serif; background: #0f1115; color: #e5e7eb; padding: 24px; } .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; } .card { background: #1a1d24; border-radius: 12px; padding: 16px; } .ok { color: #22c55e; } .bad { color: #ef4444; } /style /head body h1Status Deck/h1 div classgrid idmetrics-grid/div div classgrid idservice-grid/div div classgrid idgit-grid/div script function connect() { const ws new WebSocket(ws://${location.host}/ws); ws.onmessage function (event) { const msg JSON.parse(event.data); if (msg.type metrics) { renderMetrics(msg.payload); } else if (msg.type service) { renderServices(msg.payload); } else if (msg.type git) { renderGit(msg.payload); } }; ws.onclose function () { setTimeout(connect, 3000); }; } function renderMetrics(payload) { const grid document.getElementById(metrics-grid); grid.innerHTML div classcardCPU span${payload.cpu_pct.toFixed(1)}%/span/div div classcardMEM span${(payload.mem_used / 1024 / 1024).toFixed(1)}M/span/div div classcardDISK span${(payload.disk_used / 1024 / 1024).toFixed(1)}G/span/div ; } function renderServices(payload) { const grid document.getElementById(service-grid); grid.innerHTML payload.map(s div classcard${s.name} span class${s.status online ? ok : bad}${s.status}/span/div ).join(); } function renderGit(payload) { const grid document.getElementById(git-grid); grid.innerHTML payload.map(g div classcard${g.repo} [${g.branch}] ${g.dirty ? dirty : clean}/div ).join(); } connect(); /script /body /html这个页面没有用到任何构建工具直接把文件放到web目录下Golang的http.FileServer就能托管。JavaScript里最值得注意的部分是断线自动重连ws.onclose之后延迟3秒重新调用connect。这个3秒的延迟不能太短否则服务端重启瞬间可能产生大量重连请求。如果以后想升级成Vue版本也不需要在架构上伤筋动骨只需要把renderMetrics这些函数的内部换成Vue的响应式更新即可。数据结构不变前端怎么渲染可以随便换。5. 实战填坑记录首版5个问题与修复5.1 第一个坑SQLite database is locked第一次把三个采集器全部跑起来的时候程序几乎立刻报错核心信息是database is locked (5) (SQLITE_BUSY)原因非常清楚多个goroutine同时向SQLite发起写事务SQLite只允许一个写者其他写请求直接失败。我的采集器有系统指标、服务探活、Git状态三路任何一路落库的动作撞到一起就报错。解决方案并不复杂。我没有把SQLite连接设置成阻塞等待而是把落库操作全部放进一个channel由一个专门的goroutine消费并执行写操作。相当于给所有写请求排了一条队伍同一时间只有一条能真正碰到数据库。这样处理后运行一整晚都没有再出现锁定报错。如果你也想用“写请求排队”的方案记得channel的容量不要设太小我设的是128对现有采集频率来说足够。5.2 第二个坑断线连接没释放CPU悄悄涨开发过程中还有一个隐蔽问题前端页面关闭或刷新时WebSocket连接如果没被服务端正确清理连接会一直残留。最开始我通过浏览器开发者工具看到网络连接一直处于Pending状态服务端的goroutine数也在悄悄上涨CPU占用比正常水平多了大概0.5%。排查后发现是没有及时处理unregister。前端刷新页面时旧的WebSocket连接会被浏览器关闭但服务端的读写循环如果挂在ReadMessage上可能一直感知不到必须等下一次写入失败才会清理。这里不能简单依赖写失败来清理更好的做法是在读循环里持续读取客户端消息一旦ReadMessage返回错误立刻把连接从Hub里注销。还需要配合一个固定的读超时设置防止连接长期假死。5.3 第三个坑采集频率太激进第一版我把系统资源采集器设成1秒一次想着反正本地小工具数据越新鲜越好。结果运行半天后发现采集本身带来的CPU开销已经接近一个可感知的水平特别是在用gopsutil采集CPU百分比时频繁的系统调用让整机负载高了近3%。这个代价对一个“监控自己系统状态”的工具来说完全不合理。后来我把采集频率调整为5秒一次服务探活10秒一次Git状态30秒一次体感刷新仍然很流畅CPU开销降到几乎可以忽略不计。这给我的教训是自用工具也要考虑采集成本不是越激进越好。你真正需要的是“足够及时”不是“无限精确”。5.4 第四个坑刷新页面丢历史数据WebSocket是主动推送模式前端打开页面后只接收之后产生的数据。一旦浏览器刷新之前推送过来的历史数据就全部丢了页面上会出现短暂的空白或默认值。对于只看实时的人来说问题不大但我想看“过去一小时的CPU曲线”时没有历史数据就完全没办法画。解决办法是在前端连接建立以后先通过HTTP接口拉取一次最近的历史数据把旧状态填进区域然后再依靠WebSocket增量更新。这个逻辑不太复杂但很容易被忽略。所以现在的Status Deck启动页面时会先请求一次 /api/history拿到SQLite里存的历史指标渲染完成后WebSocket消息才会覆盖或追加数据。这个交互模式值得记住实时推送到前端的数据适合“增量更新”但首次加载必须搭配历史数据的初始化体验才会完整。5.5 第五个坑定时任务里找不到git命令Git仓库状态采集器在终端手动运行时一切正常但当我把它放进cron定时任务里后服务却报错“exec: git: executable file not found in $PATH”。原因是cron启动环境里的PATH和交互式终端不一样很多系统命令的路径没有被包含进来。解决方法是写代码时不要完全依赖PATH先通过exec.LookPath找到git的绝对路径或者直接在代码里用/usr/bin/git执行。这里要小心不同系统的差异如果目标机器是macOSgit通常在/usr/bin/git如果是自制Homebrew安装可能在/opt/homebrew/bin/git。我后来在配置里加了一个cmd_path字段用户可自行指定这样跨平台部署的时候就稳了。这个坑也提醒我自用工具也要考虑运行环境不能觉得“反正我自己跑没问题就行”。6. 跑完0.1之后的几点体会0.1版本跑通后我连续用了一周每天早上打开Status Deck扫一眼确实省了不少重复劳动。但比“省时间”更重要的是我通过这个项目验证了一套自用工具应有的设计思路。第一个体会是自用工具的核心是“先跑到能用”不是“先构造完美架构”。我之前有过不少半途而废的项目大多数是倒在“想太多”上面。这次我严格控制MVP范围只做P0功能两天就把链路拉通了。很多设计的不足都是运行之后才暴露出来的而不是一开始设计就能避免。第二个体会是数据分层是后续扩展的地基。SQLite存储历史数据这个决策看起来微不足道没有它后面的趋势图和AI异常分析无从谈起。哪怕第一版还没用上趋势图数据表结构也要先留在那里。第三个体会是这种小工具的价值不在于大而全而在于把你每天真正要看的信息放到第一屏。Status Deck不监控整个机房不统计全公司服务它只关心“我的服务器”“我的仓库”“我的任务”。这种私有化、个性化的定位才是它让我真正愿意每天打开的原因。后面我会接着写系列第二篇把Status Deck用uniapp改造成手机端可访问的版本再往后是加上AI异常摘要和趋势分析。现有这套Golang加WebSocket的架构足够支撑这些后续扩展关键的坑也在第一篇里提前排掉了一部分。如果你也在考虑做一个属于自己的仪表盘强烈建议先从一个最小的版本开始跑起来后再迭代。
返回列表