广州十一月的雨总是断断续续。坐在图书馆靠窗的位置,屏幕上的代码光影映在玻璃上,和窗外昏黄的路灯交叠。在这个被称为民办三本的校园里,我已经度过了将近四年的时间。
很多人觉得这样的起点意味着某种定局,但我似乎习惯了在安静的角落里寻找自己的节奏。绩点 4.02,两次国家奖学金,几个国二的竞赛证书,这些在外人看来或许是某种证明的文件,现在都静静地躺在我的抽屉里。它们代表了过去的时间,但我更在意的是当下键盘敲击时,那些正在生成的、鲜活的逻辑。
做 Idrop.in 这个项目,起因是一件极其微小却又常常让人感到无力的事情。
临近毕业,班委收作业、老师收报告、社团收材料,各种名目的文件收集需求充斥着日常。每次都是在微信群里疯狂地发通知,然后看着群聊被各种文件淹没。微信的文件有大小限制,还会过期;交上来的文件格式五花八门,命名更是千奇百怪。有人叫“新建文档(1).docx”,有人叫“最终版绝对不改.pdf”。收集者需要一个个下载,核对名单,再手动重命名,最后打包。
这是一种毫无意义的机械劳动。人应该去做更有创造力的事情,而不是充当一个人肉文件分类机。
Idrop.in 想解决的就是这个问题。它的逻辑很简单:创建一个收集链接,分享出去,别人在浏览器打开就能上传文件。系统会自动归类、自动按规则重命名,收集者最后只需要点击一下,就能拿到一个整理好的压缩包。
在技术选型上,我没有追求所谓的时髦,而是选择了自己最熟悉也最能掌控的栈。前端是 React 配合 TypeScript,样式用了 Tailwind CSS。后端是传统的 Spring Boot,存储层抽象了 OSS 和 MinIO,部署则交给了 Docker 和 Nginx。
写前端的时候,TypeScript 给我带来了一种很安定的感觉。它像是一份契约,定义了数据应该长什么样。在处理文件拖拽上传这个核心交互时,我没有用太多臃肿的组件库,而是基于原生 API 配合 Tailwind 慢慢调优。Tailwind 的好处在于你不需要在 HTML 和 CSS 文件之间来回切换,所有的样式思考都停留在一个层级。
不过在实现拖拽区域时也遇到过一些细节问题。比如浏览器默认会拦截拖拽文件的行为并直接在当前标签页打开文件。我需要在全局监听 dragover 和 drop 事件,并调用 e.preventDefault()。为了让用户在拖拽文件进入区域时有明确的视觉反馈,我加入了一个微小的状态 isDragging,当它为 true 时,边框会变成柔和的蓝色,背景会有一层极淡的阴影。这些细节并不显眼,但用起来会觉得顺手。
后端用 Spring Boot 是因为它的工程化体系非常成熟。Java 虽然有时候显得啰嗦,但在处理复杂的业务逻辑时,它的严谨性是无可替代的。
在文件上传和存储这部分,我踩过一个不小的坑。最初为了图省事,文件上传后我是直接写到本地磁盘的临时目录,然后再同步到 OSS。这在单人测试时毫无问题,但当模拟并发上传或者处理大文件时,服务器的 IO 和内存压力会瞬间飙升。后来我重写了这一层,直接利用流(Stream)将前端传来的 MultipartFile 的 InputStream 对接到 MinIO 或阿里云 OSS 的 SDK 中,实现了内存的零拷贝流转。
打包下载功能也是一个容易出问题的地方。当一个收集任务包含了几百个文件,如果先把它们全部从对象存储下载到服务器内存,再进行 Zip 压缩,一台普通配置的服务器很快就会抛出 OutOfMemoryError。我的解决方式是使用 ZipOutputStream,边从对象存储读取文件流,边写入到 HTTP 的 HttpServletResponse 输出流中。这样无论文件总大小是多少,内存的占用始终保持在一个极低的水平。看着进度条平稳地走完,那种符合预期的确定性,是写代码最迷人的时刻之一。
Idrop.in 的核心功能并不复杂,但每一个设计都试图去贴合真实的场景。
创建收集任务时,可以设置截止时间、文件类型限制和命名规则。命名规则是我花了一些心思的地方。我设计了一个简单的模板引擎,比如收集者可以设置规则为 {姓名}-{学号}-实验报告,提交者在上传时,页面会强制要求他们填入姓名和学号,最终生成的文件就会严格按照这个格式命名。这种在源头就切断混乱的设计,比事后去写正则重命名要优雅得多。
系统还支持智能文件管理,会自动按提交者对文件进行分组。后端通过关联提交者的唯一标识(比如学号或手机号)和文件记录,在前端渲染出一个清晰的列表。你可以实时看到进度追踪,谁交了,谁没交,一目了然。不再需要对着 Excel 表格一个个打勾。
为了方便传播,每个任务都会生成专属的短链接和二维码。无论是贴在教室的黑板上,还是发在微信群里,都很自然。
完成这个项目的那个晚上,我敲下 docker-compose up -d。看着终端里一行行绿色的 Done 依次亮起,Nginx 成功代理了流量,SSL 证书也顺利签发。我用手机扫了一下屏幕上的二维码,上传了一张桌面的截图。两秒钟后,后端的控制台打印出了文件上传成功的日志,存储桶里多了一个命名规整的文件。
一切都在安静地运转。
在广州商学院的这几年,我见过很多人抱怨环境,抱怨资源。我也曾迷茫过,但后来我发现,写代码是一件绝对公平的事情。编译器不会因为你身处三本院校就对你的语法错误网开一面,也不会因为你拿了国奖就自动帮你优化算法复杂度。你付出的每一分思考,最终都会如实地反映在程序的运行效率和用户体验上。
Idrop.in 或许只是互联网汪洋中的一叶扁舟,它可能没有几万的日活,也没有复杂的分布式架构。但它是完整的,它切实地解决了我生活中的某一个痛点,并且它是按照我理解的优雅方式去构建的。
雨好像停了。图书馆的人渐渐少去,空气里有一种潮湿但清新的味道。我合上电脑,把代码推送到 GitHub。这只是一次普通的 commit,也是我大学生活里无数个普通夜晚的缩影。
如果你也经常被收集文件这件事困扰,或者对这个小工具的实现细节感兴趣,可以看看下面的源码。
GitHub: idropin | 前端仓库 | 后端仓库