ARTICLE · 1137215
不用任何框架,我把整个下载站从零写完了
前几天我把这个站重新数了一遍:107 个 PHP 文件,12812 行代码。
数完发现一件事——整个项目没有一个第三方依赖。没有 Composer,没有 npm,没有前端框架,没有图标库,也没有图表库。
一开始不是刻意为之,原因很土——我没打算装。一个软件下载站的功能边界很清楚,列表、详情、下载、登录、后台增删改查。这个体量上框架,抽象带来的成本比它省下的代码更多。
但「不用框架」不等于「什么都自己造」。PDO 我没造,password_hash 我没造,json_encode 也没造——这些是 PHP 自带的,不该重造的东西一个都没重造。
这篇文章就把零件一件件摊开讲。
一个下载站,总共 107 个文件
先看整体。目录就分五块:
public/—— 唯一对外暴露的目录,Web 服务器只指向这里 app/—— 所有代码:路由表、内核、控制器、模型、模板 config/—— 站点、数据库、上传、邮件、安全配置 database/—— 建表脚本和初始数据 storage/—— 日志、缓存、会话
Web 根只有一个目录,这是最省事也最有效的安全边界。app/、config/、storage/ 全在它外面,浏览器再怎么构造路径也走不进去。
再往下细分:路由表 1 个文件,内核 19 个类,前台控制器 8 个、后台控制器 14 个,模型 13 个,模板 48 个。
样式和脚本也没用构建工具:前台 1631 行 CSS,后台 530 行,JavaScript 加起来 813 行。

Web 根只有一个目录,其余全在它外面
入口只有 12 行,路由是一张表
每个请求先打到 public/index.php。这个文件是全文最短的代码之一,一共 12 行,只做四件事:
记下起始时间 算出项目根路径 引入启动文件 启动应用
没了。它不解析配置、不连数据库、不判断环境——那些都在启动文件里按顺序发生,入口本身只管把门打开。

整个站的入口,12 行,不多一行
然后是路由。所有地址都写在一张数组表里,一条路由就是一行,五个位置依次是:请求方法、路径、控制器和方法、路由名、中间件。
以详情页那行为例,GET 请求、路径是 /software/{slug}、交给软件控制器的 show 方法、名字叫 software.show。91 条路由就是 91 行,从头扫到尾能看完。
中间件写在最后一列。标了 admin 的必须是管理员,标了 auth 的必须登录。后台那 50 条路由全部挂着 admin,不需要到每个控制器里再写一遍判断。
路由匹配是自己写的,支持 {slug} 这种占位参数。好处是出了问题我不用先猜是哪一层框架的锅。

一条路由就是一行,中间件写在最后一列
19 个内核类,每个只干一件事
内核放在 app/Core/,一共 19 个类。其中真正算「框架」的只有一部分:
Router 匹配地址,Request 封装请求,Response 负责输出与下载 View 渲染模板,Model 组织查询,Database 包一层 PDO Auth 管登录态,Validator 管校验,Uploader 管上传
剩下的都是工具:会话、CSRF、分页、图标、邮件、日志、配置。
每个类只管一件事,名字就是它干的事。我特意没做服务容器、依赖注入那一套——19 个类里没有一个需要运行时装配,用到就直接调,找起来比翻容器快。
代价当然有:没有生态。想加个队列、换个缓存驱动,得自己写。但看这个站眼下的实际需要,这些确实都用不上。
下面这页后台,就是这套内核跑出来的结果。左边是导航,右边几张指标卡加四个图表,每一块都是那 19 个类里的某几个在处理。

这页上的图,也是那 19 个类跑出来的结果
17 张表,没有一张是纯装饰
数据库一共 17 张表,分四组看:
- 内容
软件主表、版本表、截图表,加分类、平台、标签三张字典表 - 用户
账号表、令牌表、收藏表、评论表 - 关联
软件和平台、软件和标签,各一张中间表 - 运营
下载日志、站点设置、公告、友情链接、后台操作日志
每一张都有实际用途,没有一张是「以后可能用得上」预留的。表少了还有个附带好处:改字段的时候不用去翻一堆关联关系。
设计时踩过一个坑,挺典型。分类表里有个字段记录该分类下的软件数量,我一开始写的是「直接属于这个分类的软件数」——结果所有顶级分类的数量全是 0。因为软件只会挂在最末级分类上,父级自然数不到。后来改成把子分类的数字聚合上来,并且一层层往父级同步,数字才正常。

一个版本对应一行数据,上传本地包时 SHA256 自动算
图标和图表,都是手画的
前端没有一个图标文件,也没引图标库。89 个图标全是内联 SVG,存在一个常量数组里,用的时候按名字取。
好处很实际:不依赖外部 CDN,不多发请求,颜色直接跟着文字颜色走。坏处也直接:加新图标得自己去找路径数据。
图表同理。后台那几张面积折线图、环形图、条形图,一张都没用图表库,都是用字符串拼出 SVG 再塞进页面。折线要平滑,就自己算控制点,能用眼睛看出来的那点弧度,背后是几行坐标计算。

89 个图标都是这样的路径数据,没有一张图片文件

平滑曲线是算出来的,不是图表库给的
框架帮不了你的那部分,还是得自己写
这是我最想说的一段:不用框架不等于不安全,用了框架也不等于安全。
下面这些事,不管你用不用框架,都得自己做:
- SQL
全部走参数化查询,排序字段做白名单——因为排序字段没法参数化 - XSS
输出统一转义;富文本按标签白名单过滤,顺手剥掉所有 on*事件和javascript:开头的链接 - CSRF
所有提交都验令牌,比较用 hash_equals,防的是时序攻击 - 上传
扩展名白名单只是第一层,还要确认它真是图片,再扫文件头特征——一个改名成 .jpg 的脚本会被直接拒绝 - 登录限流
连续失败 5 次锁 15 分钟,后台单独计数 - 目录穿越
删文件前先确认目标路径落在上传目录内
这些写在框架里,反而更容易被忘掉——因为你会默认「框架应该处理了吧」。

读者看到的就这一页,看不到的是它背后那一整套校验
最后说点实话
不用框架从来不是目的。如果这是个电商站,要接支付、要做多端同步、要按业务快速堆功能,我第一个装框架——那种复杂度不是手写能扛住的。
选自己写,只是因为这道题的规模刚好卡在那条线上:功能边界清楚,表不多,页面形态也稳定。而我对它有长期打算,自己写的东西,改起来不用等别人发版。
站还是那个站,打开是一页干净的下载列表。你要是也想看看它长什么样,点开就行:goods.ha0ran.icu
觉得有用?点个关注,持续获取优质内容。