图片上传网站
图片上传,图片听起来是上传个再简单不过的功能。可真正动手做起来,网站才发现这里面藏着不少坑。图片
我去年给一个摄影社区做过一次后端重构,上传用户每天上传的网站图片超过十万张。起初架构很简单,图片Nginx直接转发到一台应用服务器,上传图片存本地磁盘。网站上线头三个月风平浪静,图片直到某天下午,上传运维突然打电话说磁盘满了。网站我登录服务器一看,图片一个月的上传图片把500GB的SSD塞得严严实实。那天晚上我临时挂了块新盘,网站把旧数据用rsync搬到另一个目录,算是勉强撑过去。
但这事让我意识到,图片上传不只是“接收文件”那么简单。你得考虑存储策略、访问频率、缩略图生成、甚至防盗链。就拿存储来说,直接存本地磁盘是最省事的方案,但扩展性极差。后来我改用对象存储,把图片扔到云上,应用服务器只保留元数据。迁移过程倒不复杂,麻烦的是存量数据——十万张图,每张都要重新上传一遍,还得保证URL不变。我写了个脚本,遍历数据库里的记录,逐个下载再传到新存储,跑了一整夜。
上传本身也有讲究。很多网站喜欢用multipart/form-data直接POST文件,简单粗暴。但遇到大图就尴尬了——一张RAW格式的照片动辄50MB,PHP默认限制才2MB,得改配置。更头疼的是网络不稳定,用户传一半断了,整张图作废,又得重来。我见过一个方案是前端先切分文件,分片上传,后端再合并。这样断点续传就自然实现了,但代码复杂度直线上升。我自己做过一次分片上传,得处理并发顺序、校验每个分片的MD5,合并时还得防止内存溢出。老实说,对于个人项目,直接限制文件大小可能更务实。
图片处理又是另一层麻烦。用户上传的图,尺寸五花八门。你不能让所有人直接访问原图——那会拖垮带宽。通常做法是上传后立即生成几种缩略图:小图用于列表页,中图用于详情页,原图只保留给付费用户或管理员。生成缩略图得用ImageMagick或libvips这类库,每次处理都会消耗CPU。我试过在请求时动态生成,结果高并发下服务器直接卡死。后来改成异步任务,上传完成后丢进消息队列,由worker进程慢慢处理。用户看到的是“处理中”状态,几秒后刷新就正常了。
还有一个常被忽略的细节:文件名。用户上传的图片往往叫“IMG_20230101_123456.jpg”这类名字,直接存下来没问题,但如果你允许用户自定义文件名,就得小心了。有人会传带中文的、带空格的,甚至带路径分隔符的。我见过一个案例,某用户上传的文件名是“../../etc/passwd”,如果后端不做过滤,直接拼接路径,就可能造成目录穿越漏洞。所以我的习惯是,存储时一律用随机生成的UUID作为文件名,原始文件名只存在数据库里,展示时再取出来。
最后说说安全。图片上传是攻击者的热门入口——上传个PHP脚本伪装成JPEG,如果服务器配置不当,就可能被解析执行。检查MIME类型只是第一道防线,真正的做法是:存储目录禁止执行脚本,上传后重命名文件,并且用白名单扩展名校验。还有人会往图片里嵌入恶意代码,叫图片马,得靠内容检测服务来识别。
做一个图片上传网站,远不止“接收文件”这么简单。存储、传输、处理、安全,每一环都能折腾得你怀疑人生。但把这些坑一个个填平,看到用户流畅地传完一张照片,那种满足感也是实实在在的。