FastAPI 5分钟搭建局域网文件剪贴板神器,可行吗?

摘要:厌倦了用微信传文件被压缩?受够了数据线插拔的麻烦?本文带你用Python的FastAPI框架,手搓一个能同时在手机和电脑之间互传文件、同步剪贴板的小工具。纯代码实战,附带防踩坑指南,小白也能看懂,老鸟也能省事。
你是不是也经历过这样的瞬间? 明明手机就在手边,想传个截图到电脑修图,结果打开微信,点开“文件传输助手”,发送,等半天,还得在电脑上登录微信,下载……一套流程走下来,修图的心情都没了。反过来,想把电脑上写好的文案发给手机,更麻烦。 至于剪贴板?手机看到的好句子,想在电脑上搜一下,要么靠手打,要么靠发条消息再复制。这哪是科技时代,这简直是“手动挡”生活嘛! 作为每天在电脑和手机之间来回切换的一名程序媛,这个问题困扰我很久了。市面上的“隔空传送”不是不好用,是生态限制太死。终于有一天,我忍不了了,决定自己动手,用咱程序员最熟悉的FastAPI,搭一个“私家传送站”。 核心摘要: 今天这篇文,不是让你读文档。我会手把手带你写一个轻量级的Web应用,跑在你电脑上。然后,只要是连在了同一局域网的手机或平板电脑等网络设备,打开浏览器,就能上传下载文件,还能同步剪贴板。全程代码不超过100行,安全、私有、还免费! 📦 先看看咱要搭的东西长啥样 想象一下,你电脑上开了个服务,手机浏览器里打开一个页面。页面上半部分是一个文件上传区,点一下,选手机里的照片,秒传回电脑指定文件夹。页面下半部分是一个剪贴板文本框,你在电脑上复制了代码片段,打开手机页面,它就在那等着你粘贴。反之亦然。 简单,直接,没有中间商赚差价。 🎯 为什么是FastAPI? 你可能会问:为啥不选Flask或者Django?好问题!我选FastAPI的原因很简单:快。这里的“快”是双关,一是它性能好,底层是异步的;二是它开发极快,自带交互式API文档,调试起来爽歪歪。对于咱们这种小工具,简直量身定做。 而且,它的文件上传处理,是我用过最优雅的,没有之一。 ⚙️ 实战:从零开始的私家传送站 好,咱们不废话,直接撸代码。我会把完整代码拆开讲,你复制粘贴就能跑。不过,先别急着复制,听我说个坑:Python版本建议3.8以上,否则有些依赖会让你怀疑人生。当初我偷懒用3.7,结果一个依赖报错,排查了一小时,血泪教训! 📁 第一步:创建项目文件夹,安装依赖 打开终端,敲几行命令: uv init file_clipboard_server # 创建虚拟环境 cd file_clipboard_server # 进入项目目录 uv add fastapi uvicorn python-multipart # 安装依赖 这里重点来了!python-multipart 这个库是必须的,没有它,FastAPI没法解析上传的文件。官方文档虽然说了,但很多人看文档不仔细,漏掉这步,然后回来问我为啥上传不了。记住了啊! 💻 第二步:编写核心代码 main.py 在项目文件夹里新建一个 main.py 文件,把下面这段代码丢进去。我加了详细注释,你就当我是边写边跟你唠嗑。 from fastapi import FastAPI, File, UploadFile, Request from fastapi.responses import HTMLResponse, FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles import os import uvicorn app = FastAPI() # 用来存放上传文件的目录,没有就自动创建 UPLOAD_DIR = "uploads" os.makedirs(UPLOAD_DIR, exist_ok=True) # 用来存剪贴板内容的简单变量(注意:重启服务就没了,但够用了) clipboard_content = "" # 主页,返回一个简单的HTML页面 @app.get("/", response_class=HTMLResponse) async def main_page(): # 这个HTML我稍后会解释,你先复制 html_content = """ <!DOCTYPE html> <html> <head> <title>私家传送站</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> body { font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; background: #f9f9f9; } .card { background: white; border-radius: 16px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); } h2 { margin-top: 0; font-size: 1.5rem; } input, textarea, button { width: 100%; padding: 12px; margin: 8px 0; border: 1px solid #ddd; border-radius: 8px; font-size: 16px; box-sizing: border-box; } button { background-color: #3498db; color: white; border: none; font-weight: bold; cursor: pointer; } button:hover { background-color: #2980b9; } .result { margin-top: 16px; padding: 12px; background: #f0f7ff; border-radius: 8px; font-size: 14px; word-break: break-all; } </style> </head> <body> <div class="card"> <h2>📎 文件互传</h2> <form id="uploadForm" enctype="multipart/form-data"> <input type="file" name="file" id="fileInput" required> <button type="submit">上传到电脑</button> </form> <div id="uploadResult" class="result"></div> </div> <div class="card"> <h2>📋 剪贴板同步</h2> <textarea id="clipText" rows="4" placeholder="在这里粘贴或查看文本..."></textarea> <button id="syncToServer">📤 同步到电脑</button> <button id="loadFromServer">📥 从电脑获取</button> <div id="clipResult" class="result"></div> </div> <script> // 文件上传逻辑 document.getElementById('uploadForm').onsubmit = async (e) => { e.preventDefault(); const file = document.getElementById('fileInput').files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); const res = await fetch('/upload', { method: 'POST', body: formData }); const data = await res.json(); document.getElementById('uploadResult').innerHTML = `✅ ${data.filename} 上传成功`; }; // 剪贴板同步 document.getElementById('syncToServer').onclick = async () => { const text = document.getElementById('clipText').value; const res = await fetch('/clipboard', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: text }) }); const data = await res.json(); document.getElementById('clipResult').innerHTML = `📤 ${data.message}`; }; document.getElementById('loadFromServer').onclick = async () => { const res = await fetch('/clipboard'); const data = await res.json(); document.getElementById('clipText').value = data.content; document.getElementById('clipResult').innerHTML = `📥 已同步: ${data.content.substring(0, 50)}`; }; </script> </body> </html> """ return html_content # 文件上传接口 @app.post("/upload") async def upload_file(file: UploadFile = File(...)): file_path = os.path.join(UPLOAD_DIR, file.filename) # 防止文件名冲突的小处理,这里简单覆盖同名文件 with open(file_path, "wb") as buffer: buffer.write(await file.read()) return {"filename": file.filename, "message": "上传成功"} # 获取剪贴板内容 @app.get("/clipboard") async def get_clipboard(): return {"content": clipboard_content} # 更新剪贴板内容 @app.post("/clipboard") async def update_clipboard(request: Request): global clipboard_content data = await request.json() clipboard_content = data.get("content", "") return {"message": "剪贴板已更新"} if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000) 这段代码里,HTML部分我故意写得比较“原始”,就是为了让大家看得懂。别嫌丑,功能第一! 🚀 第三步:跑起来! 在终端运行: uv run main.py 看到 Uvicorn running on http://0.0.0.0:8000 这样的提示,就说明成功了! 现在,拿起你的手机,连上和电脑同一个Wi-Fi,打开浏览器,输入 电脑的局域网IP:8000。怎么看电脑IP?Windows用 ipconfig,Mac/Linux用 ifconfig,找到类似 192.168.x.x 的地址就行。 是不是以为这样就完了?别急,这里有个容易翻车的点:防火墙。如果手机死活打不开,八成是电脑防火墙拦住了8000端口。去防火墙设置里加一条入站规则,允许8000端口访问。我当时就卡在这步,折腾了好久。 🎉 成品长这样 当手机页面打开的那一刻,你会看到一个干净的两块区域。点“上传到电脑”,你手机里的图片或文件就嗖的一下飞到电脑的 uploads 文件夹里了。 在电脑上复制粘贴一段文字到文本框中,点击“同步到电脑”,在手机上点“从电脑获取”,它立刻就出现了。反过来,在手机上输入文字,点“同步到电脑”,这个文字就被存到了服务端的变量里,你在电脑上随时可以调用。 这个功能纯属顺手一加,结果成了真香现场。我经常在手机上刷到好文章,复制金句,点一下同步,电脑上打开IDE写文章时,直接就能贴上去。无缝衔接! ⚠️ 几点不得不提的注意事项 - 安全性:这个小工具只建议在内网(家里/公司Wi-Fi)使用。如果暴露在公网,没有做任何鉴权,别人也能访问,风险很大。别图方便把端口映射出去! - 剪贴板持久化:我们这里用了内存变量,服务重启就没了。如果想持久化,可以改成存文件或数据库,代码改动很小,留作你的课后作业吧。 - 大文件上传:如果传视频这种大文件,可以加上进度条,或者用分片上传。但作为日常传点照片文档,这个版本绰绰有余。 🚧 进阶思考:还能怎么玩? 当你把这个小东西跑起来之后,你会发现它的潜力远不止这些。你可以把它改成一个临时的“公共相册”,朋友们聚会时扫码上传照片;或者把它变成一个跨平台的“写作同步工具”,在手机上写大纲,电脑上直接继续。甚至,你可以用类似的方法,实现电脑控制手机播放PPT?脑洞大开的时刻来了! 这个工具的选择,好比选螺丝刀,不是最贵的就好,而是顺手、能解决问题的就是最好的。FastAPI这个小螺丝刀,我用得挺顺手,希望你也是。
好啦,以上就是今天的全部内容。 如果你也跟着跑起来了,恭喜你,又多了一个专属的效率工具!如果卡在某个环节,欢迎留言,我会第一时间帮你看看,毕竟这些坑我也都踩过。 ❤️ 如果这篇文章帮到了你,点赞、收藏、关注支持一下~ 你的支持是我继续分享“踩坑笔记”的最大动力! 咱们下篇再见,继续聊聊那些让生活更简单的技术小玩意儿。