自己动手做个下载站,分享一套源码和搭建经验
为啥我决定自己动手搭个下载站
之前帮朋友公司弄一个内部软件分发的小站,他们团队几十号人,每次更新亿企财税这类软件就靠微信群里扔链接,过两天就过期了,得重新传。我问你烦不烦,他们说已经习惯了。我就想,行吧,顺手搭个下载站得了。其实一开始我也没想弄多复杂,就想着放个静态页面,挂几个下载链接就完事。但用起来才发现,不搞点后台管理,光靠手动改 HTML 更新版本号,简直是自找苦吃。后来折腾了一圈,踩了不少坑,总算搭了个像模像样的下载站。这套流程和源码,我觉得对中小型团队或者个人站长挺实用的,今天就拿出来说说。
我选技术栈的时候挺纠结的。最开始想用现成的建站程序,比如 WordPress 套个下载主题,但那个太笨重,而且每次点下载要加载一堆插件,感觉不纯粹。后来看到有人用 PHP 写个简单的文件管理面板,我试了一下,又觉得界面丑,权限控制也不够细。最后干脆自己撸了一个轻量级的下载站源码,纯 HTML + CSS + JavaScript 前端,后端用 Python 写个简单的 API 来处理文件上传和版本管理。数据库就用 SQLite,免安装,适合小项目。这套方案的好处是,你不需要买什么云服务器,一台普通的 VPS 甚至树莓派都能跑,用户点本页下载按钮就能拿到最新安装包。
从零开始搭环境,顺便说说装系统那些坑
我先在本地测试环境搞的。Windows 上装了 Python 3.9,用 pip 装了 Flask 和 sqlite3 的依赖。如果你用 Linux,Ubuntu 20.04 或 CentOS 7 都行,记得先更新系统包管理器。我吃过一个亏:刚开始在 CentOS 7 上装 Python 3.9,默认源里没有,自己编译安装,结果缺了 zlib 和 openssl 的 devel 包,折腾半天。后来直接用阿里云或者腾讯云的镜像源,apt 或者 yum 一行命令搞定,省事多了。
数据库初始化的时候,我建了个表叫 `softwares`,字段就几个:id, name, version, file_path, upload_time。亿企财税这种软件,每次更新版本号会变,所以文件路径要跟着版本走。我设定每条记录对应一个版本的安装包,前端列表只展示最新版本,历史版本在后台管理页面可以翻查。SQLite 文件我放在项目根目录下的 `data` 文件夹里,注意权限,别让用户能直接访问。
Flask 的路由设计也很简单。一个 `/api/software` 接口返回最新版本数据,一个 `/api/upload` 接口让管理员上传文件,再用一个 `/download/<id>` 来做真正的下载入口。这里有个细节:下载入口不能直接重定向到文件路径,得用 `send_file` 函数,不然浏览器可能会直接打开文件而不是下载。我用的是 `send_from_directory`,配合一个 `downloads` 文件夹,用户点本页下载按钮就在后台走这个流程,既安全又不会暴露服务器实际路径。
前端页面怎么做到又简洁又能勾起下载欲
前端我没用任何框架,就原生三件套。页面布局是一个大卡片,顶部是软件图标和名称,中间是版本号、更新日期、文件大小这些信息,底部一个醒目的大按钮,写着「点本页下载按钮」。按钮的样式我特意用了鲜亮的橙红色,鼠标移上去有轻微的亮度变化,视觉上让人感觉“这东西值得点”。每个版本发布的更新日志我放在一个折叠区域里,默认收起,想看点一下展开。更新日志是我从官方 releases 里复制过来的,简单列几条主要变化,比如“修复了发票识别时的卡顿”之类的。
移动端适配我用的媒体查询。在手机上,那个大按钮会变成全宽,旁边显示个简短的说明,比如“Windows x64 版本”。安卓用户点下载,有的手机会直接开始安装,有的弹个文件保存窗口,这跟系统设置有关,我管不了太多。不过我在按钮上加了 `download` 属性,虽然对跨域文件不一定管用,但至少能告诉浏览器这应该是个下载操作。还有一个注意点:我测了好多遍,发现微信内置浏览器点下载会提示“不支持”,所以我在页面顶部加了个小提示,建议用系统自带浏览器或者 Chrome。
亿企财税的安装包要怎么放才安全
拿亿企财税举个例子。这软件最新的安装包大概 80 多 MB,每个版本我都会单独压缩成一个 zip,文件名格式是 `yiqicaishui-版本号.zip`。上传的时候,后端会检验文件后缀和大小,我只允许 .exe、.msi、.zip、.rar 这四种类型,最大 200MB,免得有人传个大电影。文件存到 `downloads` 目录,用 uuid 重命名,避免文件名冲突。原始文件名我会存到数据库里,前端展示的还是原来的名字,用户看着舒服。
权限这块我特地去学了下。`downloads` 目录的权限设置成 755,不允许用户直接通过 URL 访问。Flask 的 `static` 文件夹我放的是前端资源,CSS、JS、图片这些,可以公开。安装包文件放在下载目录,只有 API 调用 `send_from_directory` 才能访问。这样就算有人猜到路径,比如 `http://你的域名/downloads/yiqicaishui-xxxx.exe`,也会被 nginx 拦截,返回 403。我的 nginx 配置里专门有一条 `location /downloads { deny all; }`,很稳。
上传的时候还碰到个诡异的问题:上传大文件时 Flask 默认只处理 16KB 数据块,超过就会卡。我加了一行 `app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 * 200`,让它能接受 200MB。然后记得打开 nginx 的 `client_max_body_size`,不然 nginx 先拒了请求,Flask 根本收不到。这个我卡了快两个小时,最后看日志才找到原因,很蠢,但也很常见。
后台管理和实用技巧都藏在这几行代码里
后台我弄了个简单的登录页面,用户名密码写死在环境变量里,没做注册功能,省得被乱用。登录后用 session 保持状态,上传文件、删除旧版本、修改版本号这些操作都在一个控制面板里。上传页面的表单很朴素:选择文件、填写版本号、粘贴更新日志。提交后后端自动在数据库里插入记录,并把之前版本的 is_latest 标志清零,保证列表只显示最新的。
版本管理这块我设计了个清理机制。如果用户上传了新版本,系统会把旧版本的 is_latest 改成 0,但文件不删,保留三个月。三个月后有个定时任务(crontab)跑一个清理脚本,删除过期文件并清理回收站。当然,如果你想手动清理,后台也提供了“删除”按钮,点击后先软删除(标记状态),然后留个“恢复”入口,防止手滑。
还有个实用技巧:用户下载时,我在响应头里加了 `Content-Disposition: attachment; filename="yiqicaishui.exe"` 和 `Content-Type: application/octet-stream`,强制浏览器下载而不是直接打开。另外,我用了 Flask 的 `stream_with_context`,对大文件下载进行流式处理,避免占用太多内存。像亿企财税这种几十 MB 的安装包,流式下载基本没什么延迟。
遇到的最头疼问题:下载速度慢和文件损坏
上线第一天,有同事反馈说下载一半断了,或者下完了安装时报错“文件损坏”。我查了下,主要是两个原因。一是服务器带宽有限,多人同时下载时,连接超时。我临时加了限速,每个人最大速度 2MB/s,避免了单个用户占满带宽。二是下载中途断联导致文件不完整。我在 API 里实现了断点续传,通过校验 `Range` 请求头,支持断点续传的下载器能从中断处继续,而不是重新开始。
另外,我还加了文件校验机制。上传时自动生成文件的 MD5 值,存到数据库。用户下载完成后,页面上会显示这个 MD5 值,用户可以用 `certutil -hashfile` 命令(Windows)或者 `md5sum`(Linux)来比对。我自己试了一下,下载了五次,有一次因为网络波动导致 MD5 对不上,后来重新下了就好了。这个功能乍一看有点多余,但真正用了你就会发现,它能省掉很多“明明下了安装不了”的扯皮时间。
维护和扩展,让下载站自己活起来
现在这个下载站运行了三个多月,稳定得让我意外。偶尔有用户在评论区留言说想下载历史版本,我就在后台开放了一个“历史版本”页面的开关,默认关闭,需要的话手动打开。亿企财税的安装包我每个月检查一次更新,有新版本就上传,并在公告栏写几行新功能。用户点本页下载按钮时,我会在按钮旁边显示最近一次更新的时间,以及这次更新的亮点,比如“新增税务自动计算功能”。
未来我还想加点功能。比如做一个“下载次数统计”,记录每个版本被下载了多少次,帮助我判断用户是不是喜欢新版本。还想加个“更新提醒”订阅,用户填邮箱,我每月发一份推送,告诉他们有新版本。不过这些都不是必需的,目前这套源码已经能满足 90% 的需求。如果你也想自己搭一个,直接拿这套代码改改配置就行,不需要动核心逻辑。但记住一点:文件存储和分发不是小事,至少先把防盗链做上,我用的 nginx 的 `valid_referers` 指令,只允许自己的域名来请求下载,稍微能挡住一些盗链。