
博客管理后台升级:仪表盘、访客地图与系统日志
博客管理后台大升级:从"能用"到"好用"
为什么要升级后台?
博客搭好之后,后台管理一直是个"能用就行"的状态——文章列表、编辑、删除,基本功能都有。但用了一段时间之后,我越来越觉得不够用:
- 每次想看博客整体数据,得一篇篇点开文章看阅读量
- 完全不知道读者都分布在哪些城市
- 服务器出问题了,只能 SSH 上去看日志
这次升级就是为了解决这三个痛点。目标很明确:让后台从"能用"变成"好用"。
仪表盘:一眼看清博客全貌

数据卡片
登录后台第一眼看到的就是5个统计卡片:文章总数、分类数量、总浏览量、项目数、技能数。
这里用了一个小优化——三个数据库查询是并行执行的:
const [stats, categories, totalViews] = await Promise.all([
getAdminStats(),
getCategories(),
db.blogPost.aggregate({ _sum: { viewCount: true } }),
]);
这三个查询互不依赖,串行执行要等三次数据库往返,并行只要一次。对于仪表盘这种"首屏加载"的场景,这个优化很有意义。
最近更新 + 阅读排行
页面中间是两栏布局——左边是最近更新的文章,右边是阅读排行榜。
排行榜的实现比较简单,就是按 viewCount 降序查询:
const topPosts = await db.blogPost.findMany({
where: { status: "published" },
orderBy: { viewCount: "desc" },
take: 8,
});
数据库排序比内存排序快很多,而且利用了索引。前3名用金银铜徽章标识,虽然只是个颜色区分,但看到自己的文章排在前面,还是挺有成就感的。
访客地图:知道读者在哪里
这是我最想要的功能。之前只有阅读量这个数字,完全没有地理维度——读者在哪里、哪个城市最多、有没有海外访问,一概不知。地图就嵌在仪表盘里,上面那张截图的中间部分就是它。
追踪原理
整体流程分三步:
第一步:用户访问博客时,前端自动发送位置请求。
// sendBeacon 在页面关闭时也能发送,不会阻塞页面加载
if (navigator.sendBeacon) {
navigator.sendBeacon("/api/track", blob);
}
为什么用 sendBeacon 而不是 fetch?因为用户可能快速切换页面甚至直接关闭,普通的 fetch 会被浏览器取消,sendBeacon 不会。
第二步:后端拿到 IP,调用 ip-api.com 查询地理位置,写入数据库。
const ip = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
const res = await fetch(`http://ip-api.com/json/${ip}?lang=zh-CN`);
const { city, lat, lon } = await res.json();
await db.visitLog.create({
data: { ip, path, city, latitude: lat, longitude: lon }
});
第三步:仪表盘地图组件从数据库读取访客数据,用 Leaflet 渲染标记。
地图选型
地图用的是 Leaflet 渲染。之前试过高德和 OpenStreetMap,都有问题——高德瓦片偶尔白块,OpenStreetMap 国内加载慢,一度换到 CartoDB 的 CDN 稳定了一阵(后来它也开始要 API Key 了,见踩坑记录)。
标记按访问次数分三种颜色:蓝色(1次)、绿色(2-5次)、橙色(5次以上)。点击标记可以看该城市的详细访问记录。
系统日志:运维不再靠 SSH

这个功能主要是为了方便日常维护。之前服务器出问题,只能 SSH 上去手动看日志,现在在管理后台就能看到。
系统信息
页面顶部有4个卡片,实时显示服务器状态:CPU、内存、运行时间、Node.js 版本。
数据用的是 Node.js 内置的 os 模块,每30秒自动刷新:
const cpus = os.cpus();
const totalMem = os.totalmem();
const freeMem = os.freemem();
内存使用率超过80%会变红,一眼就能看出服务器是不是快要扛不住了。
日志面板
下方是终端风格的日志面板——黑底白字、等宽字体,开发者一看就懂。
日志支持三种来源:开发环境日志、PM2 日志、宝塔日志。系统会自动检测并合并显示,不用手动切换。错误日志标红,警告标黄,普通信息标绿,一眼就能找到问题。
后续迭代:简历管理与数据库管理
后台上线之后并没有停下来——用着用着就会长出新的需求。后来又加了两个大模块,一并写在这篇里。
简历管理:改简历不用再进数据库

简历站的全部内容(个人信息、项目、技能分类、技能、成就、荣誉与资质)都存在数据库里。以前想改一条项目描述,得打开数据库客户端手动 UPDATE,心里总有点发慌。
现在后台里有一个「简历管理」页面:七个标签页分区管理,拖住圆点图标就能拖动排序,点眼睛图标控制这条内容在前台显不显示。最新上线的「荣誉与资质」模块也一样——后台增删改查,前台即时生效。
数据库管理:把表结构画出来

这个页面是「学习 + 运维」两用的。当初设计博客表结构的时候,表和表之间的关系全靠脑子记,时间一长就忘了谁关联谁。
现在「数据库管理」页面把全部数据表按「认证系统」「博客系统」分组画成关系图,每张表列出关键字段和关联指向,再配上建表语句、数据浏览和一键导出 Excel。查数据再也不用登服务器敲 SQL 了,点开就能看。
文章管理优化
移动端适配
之前文章列表在手机上有个问题:操作按钮(编辑、删除)是 hover 才显示的,手机没有 hover,按钮就看不见。
解决方案是移动端改用卡片布局,操作按钮始终显示在卡片底部:
@media (min-width: 768px) {
.md\:hidden { display: none; }
.hidden.md\:block { display: block; }
}
桌面端保持表格,移动端自动切换卡片——一个组件,两种布局。
置顶功能
给文章加了 isPinned 字段,置顶文章在所有列表中排最前面:
orderBy: [
{ isPinned: "desc" },
{ publishedAt: "desc" },
]
踩坑记录
Leaflet 地图一片灰
试了三个瓦片源才找到能用的。OpenStreetMap 国内加载慢,高德偶尔白块,最后用了 CartoDB 的 CDN 才稳定下来。教训是:国内环境选瓦片源,一定要实际测试。
CartoDB 瓦片也开始要 API Key
地图稳定跑了没几个月,CartoDB 的瓦片开始大面积出现 "API KEY REQUIRED" 水印——免费瓦片源的政策说变就变。教训升级了一版:依赖免费第三方服务,就要接受它随时变卦;要么自建瓦片,要么把切换瓦片源做成一分钟就能改完的事。(这次的修复过程,下一篇更新里写。)
NEXTAUTH_SECRET 丢失
Next.js 的 standalone 模式不会自动读取 .env 文件。部署后登录页面一直报 MissingSecret,排查了半天才发现是环境变量没注入。最后在构建脚本里自动注入 dotenv 到 server.js 才解决。
地图初始化报错
Leaflet 初始化时如果 DOM 还没准备好,会报 _leaflet_pos 错误。加了个 200ms 的延迟就好了——虽然有点 hack,但在实际使用中没问题。
技术栈总结
- 前端:Next.js + Tailwind CSS + Leaflet
- 后端:Next.js API Routes + Prisma ORM + MySQL
- 地图:Leaflet + 免费瓦片源(选型随政策变动,保留切换余地)
- 部署:宝塔 PM2 + Nginx 反代
这次升级没有引入新的重型依赖,Leaflet 和 ip-api.com 都是免费的,纯 CSS 实现了排行榜图表。保持了项目的轻量性。
初稿 2026年8月26日 · 9 月更新:修复配图、新增简历管理与数据库管理模块