别再手贴图标了:我给下载站写了套自动匹配
下载站刚上线那阵子,首页的卡片是这样的:清一色的灰盒子,写着文件名和大小。有同学来问:"PS 是哪个?LR 又是哪个?"
也对,一群安装包躺在列表里,长得一模一样,确实分不清。
我试着手动给几张卡片加了图标——Photoshop 配 PS 标志,系统镜像配 Windows 标志,鼠标悬浮还能弹出一句说明。效果立竿见影,问题也跟着来了:资源会越来越多,靠手贴,总有贴不动的那天。更麻烦的是文件还会改名、换目录,手工贴的图标一旦对不上,比不贴还尴尬。
所以我给它写了套自动匹配。
三条朴素的设计原则
动手之前先想清楚了三件事。
第一件是记录:匹配过的文件写进一个映射文件,下次直接跳过,不再重复判断。这保证扫描再多次,成本也不会随着文件数量爆炸。
第二件是增量:每次只处理"新出现的文件"和"缺图标/缺说明的文件"。老文件稳如泰山,新文件自己走进流水线。
第三件是兜底:匹配不到的不硬凑,用站点 Logo 顶上,说明留空。宁可安静,也不乱配。
自动化不是替你把所有事做完,而是让新来的东西自己找到位置。
匹配是怎么发生的
核心是一张关键字规则表。文件名和分类名一起丢进去,从上到下逐条匹配,命中即停,一次同时给出图标和一句说明。比如 photoshop、ps20 命中 Photoshop;lightroom、lrc 命中 Lightroom Classic;wepe 命中微PE工具箱。
顺序是这张表最讲究的地方。应用类规则必须排在系统类规则前面——不然文件名里带个 windows 的第三方软件,就会被系统镜像规则抢走。系统类内部也一样:windows_7 要排在泛化的 windows 前面,先具体,后笼统。这些坑都是跑起来之后才踩出来的。
规则表之外,映射文件里还有第二层优先级:只要某个文件已经被记录过,就永远先看记录。这给"手动改"留出了空间。
自动化里最人性的部分:手动优先
后台有一个"资源匹配"页,列出了所有资源。任何一个文件的图标和简介都能直接改:图标可以在下拉里选"按规则自动""页面 Logo"或者图标库里的任意一个,简介改文本框就行。点保存,这条记录会被标上手动标记,从此脚本每次跑过它都会绕开,绝不覆盖。
想恢复自动也简单,把下拉选回"按规则自动"即可。
做这个功能的时候想的是:机器负责猜,人负责拍板。猜错了不丢人,覆盖人的决定才丢人。
还有一套联网兜底
规则表总有覆盖不到的品牌。对这种"漏网之鱼",还留了一手:联网匹配。
脚本会从文件名和分类里猜品牌——比如 JetBrains Rider 猜成 rider,Cisco 的 anyconnect 猜成 cisco——然后去 simple-icons(一个开源品牌图标库)抓对应图标,按品牌色生成一张图标,说明就用品牌名。抓回来的清单和品牌表会缓存 7 天,不会每次都去打扰别人。
当然,服务器得能访问图标 CDN;访问不了就安静地回退,不报错、不阻塞。
一些琐碎但重要的细节
横版的长 Logo(比如锐捷)塞进 46×46 的方块里会小得看不清,于是给这类图标单独开了一个"加宽"名单,容器最大宽度放宽,保持比例。
文件被重命名或移动后,路径会变,旧记录自动清理,新路径按新文件重新走一遍匹配——不需要人工维护。
匹配脚本是唯一会写映射文件的入口,首页只读不写。这一步在早期救过我们:不然打开一次首页,没匹配上的新文件就会被"锁"成兜底值,再也不会重试。
技术实现:映射、规则与增量
四块积木,各管一段:
mu-plugins/llx-downloads.php # 规则函数 + 页面渲染(只读映射)
/usr/local/bin/llx-icon-match # 匹配脚本(唯一写映射的入口)
/usr/local/bin/llx-icon-online # 联网匹配(simple-icons)
uploads/llx-icon-map.json # 映射文件(www-data 所有,644)映射文件就是一份 JSON:键是文件相对路径,值是图标和说明,手动改过的会带一个标记:
{
"updated": "2026-09-26 12:00:00",
"items": {
"Adobe/PS2026_27.7_PVP.zip": {
"icon": "photoshop.svg",
"desc": "Adobe Photoshop 2026 · 图像处理软件"
},
"实用工具/图吧工具箱202608安装包.exe": {
"icon": "tubatu.svg",
"desc": "图吧工具箱 · 硬件检测与系统维护",
"manual": true
}
}
}规则表写在插件里,每行长这样——图标、说明、关键字一次给全:
array(
'icon' => 'photoshop.svg',
'desc' => 'Adobe Photoshop 2026 · 图像处理软件',
'keys' => array('photoshop', 'ps20', 'ps 20'),
),匹配时把文件名和分类名拼在一起,忽略大小写,从上往下命中即停,所以顺序就是优先级:应用规则排最前,系统规则里具体的排在笼统的前面。图标文件放在主题的 assets/icons/ 目录,文件名区分大小写。
增量逻辑其实就三句话:已有真实图标的文件直接跳过;新文件或图标缺失的文件进入匹配,命中就写进映射,没命中就记进"未命中"清单;被删除的文件清掉映射记录。浏览器侧只读映射、不写入,避免打开一次首页就把新文件"锁死"在回退值上。
联网匹配走 simple-icons(经 jsdelivr CDN):先从文件名加分类猜品牌,抓到图标后按品牌色生成一张,说明用品牌名。图标清单和品牌表缓存 7 天,存在 uploads 目录下;服务器访问不了 CDN 就安静回退,不报错、不阻塞。
横版 Logo 的加宽名单是一个小函数:
function llx_dl_icon_wide($file) {
return in_array($file, array('ruijie.png'), true);
}加上之后,页面会给这类图标容器一个 wide 样式,最大宽度放宽到 120px,保持比例不变。
跑起来是什么样
在后台点一下"开始匹配",或者命令行执行一条命令,输出大致是这样:
总文件: 23
已匹配(跳过): 22
本次新增: 0
更新: 1
~ 实用工具/图吧工具箱202608安装包.exe => tubatu.svg | 图吧工具箱 · 硬件检测与系统维护工具箱大多数时候,增量就是这个样子:只动一条。我要做的只是把文件丢进目录,剩下的事它自己会办。
写在最后
后来我把这套思路挪到了别的地方:凡是"存量数据要补元信息"的场景——给文件配图标、给文章配标签、给图片配描述——都可以用同样的结构:先记录,再增量,留手动口子,做好兜底。
至于效果,最直观的一条反馈是:没人再问"哪个是 PS"了。
你们的资源库、文档站里,有没有类似的"灰卡片墙"?评论区聊聊,说不定下一篇就写你们遇到的问题。
本文由个人实践整理,路径与文件名示例均已做脱敏处理。