当前位置: 首页 > news >正文

从一个传文件的破需求,到一个能挂公网的“瞬传“:我用 WorkBuddy 把它从 HTML 一路做到了 Java

作为一个天天连服务器的人,我的痛点很具体:跨机器搬运小数据,成本高得离谱

我连生产/测试机大多走向日葵或者远程桌面。干活没问题,搬东西是真折磨。本地一个改好的配置、一段现场报错日志、一个临时打的 jar,要送到那台机器上——微信文件助手得两头各登一次;走网盘得先传再下,还留记录;rz/sz在弱网下能卡你到怀疑人生。文字比文件更糟:远程桌面里的剪贴板时灵时不灵,一段 nginx 配置、一串临时 token,经常只能对着屏幕一个字一个字手敲。

我要的东西其实极简:一个网页,丢进去,对面输个码就拿到,用完自动销毁,全程不注册、不登录、不留历史。市面上的工具要么太重、要么强制登录、要么把你每一次传输都记下来。没有一个戳中我。

那就自己写。后端 Java 我没问题,前端是真不想碰——正好让 WorkBuddy 陪我从头走一遍,看看它到底能帮到哪一步。

第一步:选对赛道

进 WorkBuddy 首页,顶部摆着几条主线:日常办公、代码开发、设计创意。我要写工程,直接点代码开发

它没急着写代码,先帮我把需求"解构"了

我没整需求文档,就把上面那段抱怨原样甩进去。它最让我意外的是:没有立刻进入写代码的兴奋状态,而是先把我的痛点抽象成了一句话——“远程环境下文件与文本的临时互传”,然后直接给了我三条技术路线,还配了一张优缺点对照表:

  • 方案 A · 纯 Web 文件中转:文件落服务器,浏览器直传直取。零配置、开箱即用,代价是文件要在服务器落地。
  • 方案 B · 实时剪贴板同步(WebSocket):双端实时,像一个在线剪贴板。文字体验极好,但只解决文字。
  • 方案 C · 综合双通道:文字 + 文件两条通道都要,前两者的优点合并。

它自己倾向C,并说明了理由。

这一步的体验已经赢了一半。我喂的是一句模糊抱怨,它回的是一个有取舍的技术选型——这才是一个工程师想要的对话方式,而不是上来就甩两百行能跑但跑偏的代码。

围绕"安全"反复对线

我没拍板,先抛了我最在意的约束:这玩意要挂公网给陌生人用,不能裸奔。它顺着这条线把安全模型拆成"用户侧"和"服务端侧"两块,颗粒度细到能直接抄进设计文档:

用户/访问侧——身份认证用一次性访问码(口令),不建注册体系,每次访问临时生成、进独立空间;分享走带 token 的邀请链接,链接自带有效期、过期即失效。上传侧——强制 HTTPS,危险后缀(exe/sh/php)走黑名单拦截,限单文件大小、限单用户总份数。下载侧——下载链接带有效期、不可被猜测的 URL,支持阅后即焚(下完一次即删)。

服务端侧——文件按用户隔离存储,互相不可见;文字内容加密落盘(类 AES,服务端只存密文);统一TTL自动清理;同 IP 限频防刷;审计日志只记时间与元信息、不记内容

它对安全的敏感度是超出我预期的。我只说了"别裸奔"三个字,它把密钥模型、隔离存储、限频、审计边界一次性铺开了。这些点后面几乎原封不动落进了最终实现。

把功能与流程钉死,顺手砍掉一半需求

安全聊透,接着定功能。我把发送方、接收方的流程口述了一遍,它顺手画了张端到端的状态流转:

发送方页面 → 上传文件/文字 → 生成唯一口令(URL)→ 设置有效期 / 下载次数 / 口令 → 接收方页面凭链接查看、下载。

然后它把多人房间单独拆成一个模块:临时空间走 WebSocket,多人输同一口令进同一房间收发文字;房间有创建者、有有效期、有人数上限;创建者可踢人、可销毁,到期消息全清。

真正值钱的是收尾那句建议——别一口吃成胖子,按交付价值拆两期:

  1. MVP:单文件/文字分享 + 链接 + 有效期/下载次数;
  2. 增强版:在 MVP 之上叠房间、二维码、密码保护。

“先做 MVP,跑通了再加功能,成本最低。”

我当时正处在"功能我全都要"的上头状态,它没有顺着我堆,反而帮我做减法。一个 AI 助手能在你兴奋的时候踩一脚刹车、把你拽回 MVP 节奏,这点比写得快重要得多。后面证明这个拆法是对的。

一张参考图,它读出了一份"设计规范"

功能定了,长相还没谱。我懒得描述,直接截了张看着顺眼的风格图丢过去。它没瞎夸,而是把这张图反解成了一份可执行的视觉规范:浅灰底 + 白色卡片、绿色主色(用于按钮高亮与选中态)、大圆角、低信息密度、顶部三大入口(接收/发送/房间)、右侧信息面板 + 二维码、底部安全提示。

随后它定了 MVP 形态,还顺嘴问要不要给项目建长期档案。我说行,起个名叫"一只牛博",照这方向开干。它回:“好,牛博,开始动手。”

从一张随手截的图到一份结构化设计 token,这一步把"我说不清的审美"翻译成了"它能落地的规则",沟通成本直接砍半。

第一版:原生 HTML/JS 先把骨架立起来

第一版来得很快,纯原生 HTML/JS,跑在localhost:3000。骨架已经齐了:顶部三 tab、中间发送区、有效期选择、右侧信息卡。糙是糙,但参考图那个味儿出来了。

我让它直接跑,它把启动指令一并给全:npm installnpm start,几行就起来了。它不只交代码,连"怎么把它跑起来"都替你想好了。

第二版:换 Vue,顺手要个毛玻璃

原生写着写着,命令式的 DOM 操作堆起来结构开始发散。我让它用 Vue 重构一版,顺便提了个我一直想要的效果——毛玻璃磨砂。它上了backdrop-filter: blur()那一套半透明,整体质感立刻不一样了。

第三版:纯抠细节,把磨砂透明度调到位

毛玻璃第一版太糊,背景插画全被磨没了,白瞎。我让它把透明度往回收,这步没技术含量、纯审美来回磨——从很高的透明度一点点压,大概落在 45% 上下,背景插画能隐约透出来又不抢前景内容。截图里我标了句"有点效果",就是这版终于看顺眼了。

值得一提的是,这种"差一点"的体感调优,它接得很稳:我不给具体数值,只说"太透了/再实一点",它能顺着语义往对的方向收敛,而不是要我报参数。

最后落到 Java:技术栈是被"部署"推着走的

聊到上线,方向变了——而且是合理地变。

这东西要长期挂公网给人用,Node 那套部署还得装运行时、起进程、配守护、挂了得拉起。我后端本就是 Java,干脆整体落到 Spring Boot 3.3.5 + Java 17:打成单个可执行 jar,配多阶段 Dockerfile(maven 先构建、产物塞进 jre 运行镜像),docker-compose一拉即起,前置Nginx 反代 + Let’s Encrypt 自动签证书

前端反而收了回来:不再背 Vue 的构建链,而是回到模块化原生 JS——features / ui / utils / api分目录拆清。后端 Java 接管所有重活:存储、限流、定时清理、口令生成、二维码(ZXing 直出)。一个 jar 梭哈,部署这件事一下就干净了。它把上线命令也逐条列了出来。

HTML → Vue → Java 这条看似跳跃的路线,其实有一条暗线:原生用来验形态,Vue 用来试交互与质感,Java 用来扛部署与安全。技术栈不是它拍脑袋换的,是跟着"这东西到底怎么用、怎么上线"自然长出来的。这种"为约束选型"的判断力,正是它专业的地方。

到这里,最终落地的工程能力已经不是一个玩具了。随手贴几个我最后定稿里的真实参数,佐证它给的不是花架子:

  • 端到端加密:前端用AES-GCM 256bit加解密,密钥编码进 URL 的#k=片段、绝不上传服务端,服务端从头到尾只摸得到密文。这正是首页"服务器只保存密文"那句话的底气。
  • 滑动窗口限流:同 IP 上传3 次/分、30 次/时,口令查询30 次/分,连续 20 次口令试错触发 10 分钟冷却——直接掐死了暴力猜码。
  • 并发闸:全局并发上传10、单 IP2,防止有人拿上传打满磁盘。
  • 分级下载配额:按体积分档,≤10MB 给 20 次、≤100MB 给 10 次、更大给 5 次
  • 定时清理:60 秒一轮扫过期内容,**最长保留 120 分钟(2 小时)**封顶。

翻开代码:它写得到底怎么样

参数好看不代表代码好。我专门挑了几段它生成的关键实现贴出来——这几段恰恰是最容易写烂、最能看出功底的地方

第一段,端到端加密的密钥处理。整个 E2E 的命门在于"密钥到底放哪"。它的做法是:密钥只编码进 URL 的#k=片段——而 URL fragment 按浏览器规范根本不会随请求发往服务端,于是服务端从物理上就拿不到密钥,只能存密文。

// 加密后,密钥拼进 URL 的 hash 片段,绝不进入请求体exportfunctionencryptedUrl(url,key){return`${url}#k=${encodeURIComponent(key)}`;}// 接收端从 location.hash 里把密钥取回来,在本地解密exportfunctionkeyFromLocation(){constparams=newURLSearchParams(location.hash.replace(/^#/,""));returnparams.get("k")||"";}asyncfunctionencryptBytes(plainBytes,rawKey){constiv=randomBytes(IV_BYTES);// 每次随机 12 字节 IVconstkey=awaitimportKey(rawKey);constcipherBuffer=awaitcrypto.subtle.encrypt({name:"AES-GCM",iv},key,plainBytes);returnconcatBytes(iv,newUint8Array(cipherBuffer));// IV 前置拼进密文,解密时再切出来}

用 WebCrypto 的AES-GCM、每次随机 IV、IV 前置拼接——这是教科书级的正确姿势,既没有自己造轮子,也没有把密钥误传上服务端。一个非密码学背景的开发者很容易在这里翻车,它没有。

第二段,限流。它用的是带时间窗的滑动计数器,而且对计数器对象做了synchronized,在并发下不会把窗口算错:

publicvoidrequire(Stringkey,intmaxCount,Durationwindow,Stringmessage){Instantnow=Instant.now();WindowCountercounter=counters.computeIfAbsent(key,ignored->newWindowCounter(now,0));synchronized(counter){if(Duration.between(counter.windowStart,now).compareTo(window)>=0){counter.windowStart=now;// 窗口过期,重置counter.count=0;}if(counter.count>=maxCount){thrownewApiException(HttpStatus.TOO_MANY_REQUESTS,message);}counter.count++;}}

一个方法靠传入的key+window+maxCount同时服务"上传按分钟/按小时"“查询按分钟”"消息按分钟"等所有场景,没有为每种限流复制一份逻辑。锁的粒度也压在单个计数器对象上、而不是整张表,并发吞吐不会被一把大锁拖死。

第三段,我最服的一处——上传并发闸为什么放在 Filter 层。它没把这个保护写进 Controller,而是单独做了个OncePerRequestFilter,并且在注释里写清了原因:

/** * Acquires upload concurrency permits before Spring parses multipart bodies. * * <p>Controller-level guards run too late for large uploads because the request * body may already be parsed. Keeping this protection at the filter layer * limits concurrent body ingestion from the same IP as well as application * processing.</p> */@ComponentpublicclassUploadConcurrencyFilterextendsOncePerRequestFilter{// ...try(TransferGuardService.Guardignored=transferGuardService.upload(ClientIpUtil.resolve(request))){filterChain.doFilter(request,response);// 拿到许可才放行,出了作用域自动释放}catch(ApiExceptionexception){writeApiError(response,exception);}}

“Controller 层的限制对大文件来说太晚了,因为请求体可能已经被解析”——这是一个踩过坑、真懂 Spring 请求生命周期的人才会写的注释。配合try-with-resources让许可自动释放,既挡住了恶意并发上传打满磁盘,又不会泄漏许可。这一段单拎出来,放进任何一个生产项目的 Code Review 都挑不出毛病。

第四段,清理调度的克制。所有过期数据(分享、僵尸上传、房间、限流计数)的回收,只用一个@Scheduled方法串起来,周期还能从配置注入:

@Scheduled(fixedDelayString="${app.cleanup-interval-seconds:60}000")publicvoidcleanup(){shareStorageService.cleanupExpired();shareStorageService.cleanupStaleUploads();roomStorageService.cleanupExpired();rateLimitService.cleanup();}

没有为每类数据各起一个定时器,也没把清理逻辑散落到各个 Service 里偷偷跑——收口到一处、依赖注入、周期可配,该简单的地方就让它保持简单。

把这四段连起来看,它的代码不是"能跑就行"的水平:关注点分离清楚、并发与边界考虑到位、注释写在真正需要解释的地方、不重复也不过度设计。说实话,这个质量已经接近一个还不错的中高级工程师的手笔了。

成品:那些被产品逻辑反推出来的设计

界面我就不挨个念了。我更想说的是,最终这套交互里有几个决策,是被"临时传输"这个内核反过来逼出来的——它们看着是 UI,本质是产品判断。这些点,WorkBuddy 在前面的对话里基本都替我想到了。

第一个决策:默认落在"接收",而不是"发送"。这个选择我很认同。发送的人是主动的,他知道自己要干嘛;接收的人是被动的,他多半是被一个口令或二维码引过来的,越早让他看到"在哪输码"越好。所以首页一进来就是接收态、一个大口令框怼在中央,把最高频、最没耐心的那条路径放在了零点击的位置。右侧那条信息栏(最长 2 小时、单文件 200MB、无需登录)则在不打扰主流程的前提下,一句话讲清了"这是个临时的东西"。

第二个决策:把"有效期"和"接收次数"做成一等公民。普通网盘的分享,过期是个藏在二级菜单里的高级选项;在这里,它俩是创建流程里跑不掉的两个旋钮——有效期(10 分钟到 2 小时)直接平铺成按钮,接收次数(默认 1 次)摆在显眼处。这不是堆功能,而是用交互把产品价值观顶到用户脸上:这东西生来就是要消失的,你必须为它的"短命"做一次决定。文本框右下实时跳的字节数和 256KB 上限,也在持续暗示边界感。

第三个决策:口令、二维码、链接,三个入口一次性全给。这是被真实场景逼的——我的原始痛点就是"跨设备",而跨设备意味着没有统一的复制粘贴通道。所以生成结果页同时吐出 8 位口令(适合念给旁边的人 / 手敲)、二维码(适合电脑发手机扫)、带密钥的完整链接(适合 IM 里甩过去),三条路通向同一份内容。值得单独说的是链接尾巴上那截#k=E6LWXY1i0Ptt...——它就是前文那段加密代码里"只活在 fragment 里、永不上送服务端"的 AES 密钥。底部「端到端加密 · 服务器只保存密文」这句话,到这里是有代码兜底的,不是贴上去好看的。

第四个决策:让安全"可被感知"。加密这件事,做了但用户看不见,等于没做。接收页每一条内容前面都挂着E2E 标记,旁边跟着剩余次数、字节数、创建时间,顶上是还在跳的销毁倒计时。用户不需要懂 AES-GCM,但他需要在那一眼里相信"这东西是加密的、是会过期的、是只给我看的"——这种把后端保证翻译成前端可见信号的处理,是很多工具会省掉、但恰恰最影响信任的一环。

第五个决策:销毁态是被当成一个正经状态来设计的,不是甩一个报错。大多数应用对"内容没了"的处理就是一个 404 或一行红字。但对一个主打"阅后即焚"的产品来说,"已销毁"恰恰是它最该讲好的故事。所以这里是一块完整的状态卡:明确告诉你"接收次数已用完或内容已过期,服务端已删除临时内容",并直接给出「重新创建」的下一步。关键是这块文案背后是真的——服务端定时任务把数据物理删了,不是前端藏起来骗你。我最初要的那句"用完自动没、不留痕",在这一屏被兑现了。

第六个决策:增强版的多人房间,真的落地了,而且是移动端优先验证的。前面设计阶段被单独拆出去、靠 WebSocket 撑起来的那个"临时空间",没有停在 PPT 上。手机进房后,顶部直接是30 人在线 + 01:58:13 销毁倒计时——把"多人"和"临时"两个最核心的属性摆在第一屏,下面才是带时间戳和字节数的实时消息流、二维码邀请和退出。一个 AI 协助搭的项目,能把二期功能也照着一期的设计语言完整收尾、并在窄屏上保持同样克制的排版,这个完成度是超出我预期的。

写在最后

整个项目断断续续聊下来,代码我几乎没自己敲,但说实话也没省到"动动嘴就行"的程度。真正花时间的是前面那些来回——三套方案选哪条、安全到底要做到哪一层、功能砍到什么程度算 MVP、参考图那个调调怎么落地。这些想清楚了,后面写代码反倒是最不费劲的部分。

一个"传文件传文字好烦"的破念头,就这么变成了一个能挂公网、扫码就用、用完自动没的小站。

http://www.jsqmd.com/news/1075522/

相关文章:

  • Serverless 架构实战:冷启动优化与事件驱动流水线的工程实践
  • 插头 DP 学习笔记
  • 2026年GEO运营的核心命题:先分析,再优化
  • GetQzonehistory:三步完成QQ空间历史数据完整备份的终极方案
  • Chrome侧边栏Gemini:浏览器原生AI工作流的实战指南
  • 复杂度的均摊分析法
  • SMC(静态分析)
  • 【232期】由夯到拉,锐评一下各种软件卸载方式!
  • 不会写代码,怎么在 3 分钟内拿到亚马逊的结构化数据?亮数据 Scraper Studio 实测
  • MuleSoft+LLM:企业级AI工作流编排实战指南
  • 金融数据科学实战:用AKShare构建你的财经数据工具箱
  • 【JAVA毕设源码分享】基于springboot“校园淘”二手交易平台的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 光污染智能监测:基于物理约束的轻量级机器学习实战
  • 杰理之音箱与手机APP连接断开【篇】
  • 2026年市面上专业人体红外感应太阳能路灯口碑推荐
  • 我必须先说一句:AI写3D代码,确实强。
  • Ryujinx终极指南:高级Nintendo Switch模拟器架构与实战配置
  • Kazumi播放器智能预览架构:深度解析缩略图生成机制
  • Agent运行时基础设施:会话、执行器与沙箱的三层解耦
  • 编写程序分析百年时装流行轮回周期,自动匹配当下复刻复古款式清单。
  • 漏洞生命周期管理与高效修复实战:从原理到DevSecOps落地
  • Seedance 2.0 深度解析:架构革新、核心能力与提示词实战指南
  • 专访蒋南青:一块退役电池的旅程,照见出海的隐秘短板
  • 牛鞭效应WebApp实验室:信息延迟、局部优化与行为偏差的动态耦合
  • Android自动化神器:AutoTask让手机智能工作,解放你的双手
  • 小米智能家居完美接入HomeAssistant的终极指南:告别米家App限制
  • 如何开始学Python
  • Open Agent SDK 用 Swift 6.1 编写,要求 macOS 13+。它在进程内跑完整个 Agent Loop:发送提示、解析响应、执行工具调用、把结果喂回 LLM,循环往复直到拿到最
  • 《C++语言程序设计教程》基础语法全解析:从入门到精通
  • 电子教科书下载工具推荐,小初高课本合集一键获取