1. 为什么我们需要一个局域网文件传输工具?
在日常工作和生活中,我们经常需要在不同设备间传输文件。传统方式存在诸多痛点:微信传输会压缩图片质量,数据线需要随身携带且频繁插拔,云盘需要登录账号且受限于网络速度。这些问题在需要频繁传输文件的场景下尤为明显。
FastAPI作为现代Python Web框架,具有几个独特优势使其成为构建这类工具的理想选择:
- 异步特性带来高性能,轻松处理多设备并发请求
- 内置自动生成的交互式API文档,方便调试
- 对文件上传的原生支持非常优雅
- 开发效率极高,适合快速构建小型工具
2. 环境准备与项目初始化
2.1 Python环境配置
建议使用Python 3.8+版本,这是FastAPI的最佳实践版本。可以使用以下命令检查Python版本:
bash复制python --version
如果尚未安装Python,推荐从官网下载最新稳定版。安装完成后,建议创建虚拟环境隔离项目依赖:
bash复制python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
2.2 安装必要依赖
在虚拟环境中安装项目所需依赖:
bash复制pip install fastapi uvicorn python-multipart
这里特别强调python-multipart的重要性 - 它是FastAPI处理文件上传的核心依赖,缺少它会导致上传功能无法正常工作。很多初学者容易忽略这一点。
3. 核心代码实现详解
3.1 项目结构设计
我们采用极简的单文件架构:
code复制file_transfer/
├── main.py # 主程序文件
└── uploads/ # 文件存储目录(运行时自动创建)
3.2 服务端完整实现
以下是main.py的完整代码,我们分段解析关键部分:
python复制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 = ""
这段代码初始化了FastAPI应用,设置了文件上传目录和剪贴板存储变量。exist_ok=True确保目录不存在时会自动创建。
3.3 前端界面实现
我们直接在Python代码中嵌入HTML,避免额外的静态文件:
python复制@app.get("/", response_class=HTMLResponse)
async def main_page():
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, max-width: 600px; margin: 40px auto; }
.card { background: white; border-radius: 16px; padding: 24px; margin-bottom: 24px; }
input, textarea, button { width: 100%; padding: 12px; margin: 8px 0; }
</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"></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"></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);
try {
const res = await fetch('/upload', {
method: 'POST',
body: formData
});
const data = await res.json();
document.getElementById('uploadResult').innerHTML =
`✅ ${data.filename} 上传成功`;
} catch (err) {
document.getElementById('uploadResult').innerHTML =
`❌ 上传失败: ${err.message}`;
}
};
// 剪贴板同步逻辑
document.getElementById('syncToServer').onclick = async () => {
const text = document.getElementById('clipText').value;
try {
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}`;
} catch (err) {
document.getElementById('clipResult').innerHTML =
`❌ 同步失败: ${err.message}`;
}
};
document.getElementById('loadFromServer').onclick = async () => {
try {
const res = await fetch('/clipboard');
const data = await res.json();
document.getElementById('clipText').value = data.content;
document.getElementById('clipResult').innerHTML =
`📥 已同步最新内容`;
} catch (err) {
document.getElementById('clipResult').innerHTML =
`❌ 获取失败: ${err.message}`;
}
};
</script>
</body>
</html>
"""
return html_content
这个HTML界面包含两个主要功能区域:文件上传和剪贴板同步。我们使用了现代CSS特性确保在手机和电脑上都有良好的显示效果。
3.4 文件上传API实现
python复制@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
file_path = os.path.join(UPLOAD_DIR, file.filename)
# 简单处理文件名冲突 - 实际项目中可能需要更复杂的逻辑
counter = 1
while os.path.exists(file_path):
name, ext = os.path.splitext(file.filename)
file_path = os.path.join(UPLOAD_DIR, f"{name}_{counter}{ext}")
counter += 1
try:
with open(file_path, "wb") as buffer:
# 分块写入,避免大文件内存溢出
while chunk := await file.read(1024 * 1024): # 1MB chunks
buffer.write(chunk)
return {"filename": os.path.basename(file_path), "message": "上传成功"}
except Exception as e:
return JSONResponse(
status_code=500,
content={"message": f"文件上传失败: {str(e)}"}
)
这个端点处理文件上传,有几个关键点:
- 使用
UploadFile类型自动处理上传文件 - 实现了简单的文件名冲突处理
- 采用分块写入方式,避免大文件占用过多内存
3.5 剪贴板API实现
python复制@app.get("/clipboard")
async def get_clipboard():
return {"content": clipboard_content}
@app.post("/clipboard")
async def update_clipboard(request: Request):
global clipboard_content
try:
data = await request.json()
clipboard_content = data.get("content", "")
return {"message": "剪贴板已更新"}
except Exception as e:
return JSONResponse(
status_code=400,
content={"message": f"无效请求: {str(e)}"}
)
剪贴板功能通过两个简单的API实现:
- GET /clipboard 获取当前内容
- POST /clipboard 更新内容
3.6 启动服务
python复制if __name__ == "__main__":
uvicorn.run(
app,
host="0.0.0.0", # 监听所有网络接口
port=8000, # 使用8000端口
reload=True # 开发时启用热重载
)
4. 部署与使用指南
4.1 启动服务
在项目目录下执行:
bash复制python main.py
看到类似下面的输出表示启动成功:
code复制INFO: Uvicorn running on http://0.0.0.0:8000
4.2 获取本机IP地址
- Windows: 在cmd中执行
ipconfig,查找IPv4地址 - Mac/Linux: 在终端执行
ifconfig,查找inet地址
4.3 从其他设备访问
确保设备在同一局域网下,在浏览器输入:
code复制http://[你的电脑IP]:8000
例如:
code复制http://192.168.1.100:8000
4.4 防火墙配置
如果无法访问,可能需要配置防火墙允许8000端口:
- Windows: 进入"Windows Defender 防火墙"→"高级设置"→添加入站规则
- Mac: 系统偏好设置→安全性与隐私→防火墙→防火墙选项
- Linux: 使用
ufw或iptables配置
5. 常见问题与解决方案
5.1 无法访问服务
可能原因及解决方案:
- 防火墙阻止了端口 - 按照4.4节配置防火墙
- IP地址不正确 - 重新确认本机IP
- 设备不在同一网络 - 检查Wi-Fi连接
5.2 文件上传失败
排查步骤:
- 检查
uploads目录是否存在且有写入权限 - 确认
python-multipart已安装 - 查看服务端日志是否有错误信息
5.3 剪贴板内容丢失
这是预期行为 - 服务重启后内存中的内容会丢失。如需持久化,可以将内容保存到文件或数据库。
6. 进阶改进建议
6.1 增加身份验证
在生产环境使用建议添加基本认证:
python复制from fastapi import Depends, HTTPException
from fastapi.security import HTTPBasic, HTTPBasicCredentials
security = HTTPBasic()
def authenticate(credentials: HTTPBasicCredentials = Depends(security)):
correct_username = "admin"
correct_password = "password"
if not (credentials.username == correct_username and
credentials.password == correct_password):
raise HTTPException(
status_code=401,
detail="认证失败",
headers={"WWW-Authenticate": "Basic"},
)
return True
@app.post("/upload")
async def upload_file(
file: UploadFile = File(...),
auth: bool = Depends(authenticate)
):
# 原有代码
6.2 文件列表展示
可以增加一个端点展示已上传文件:
python复制@app.get("/files")
async def list_files():
files = os.listdir(UPLOAD_DIR)
return {"files": files}
6.3 文件下载功能
添加文件下载支持:
python复制@app.get("/download/{filename}")
async def download_file(filename: str):
file_path = os.path.join(UPLOAD_DIR, filename)
if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail="文件不存在")
return FileResponse(file_path)
6.4 剪贴板历史记录
使用SQLite实现简单的历史记录:
python复制import sqlite3
def init_db():
conn = sqlite3.connect("clipboard.db")
c = conn.cursor()
c.execute("""
CREATE TABLE IF NOT EXISTS clips (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
conn.close()
init_db()
@app.post("/clipboard")
async def update_clipboard(request: Request):
global clipboard_content
data = await request.json()
content = data.get("content", "")
conn = sqlite3.connect("clipboard.db")
c = conn.cursor()
c.execute("INSERT INTO clips (content) VALUES (?)", (content,))
conn.commit()
conn.close()
clipboard_content = content
return {"message": "剪贴板已更新"}
@app.get("/clipboard/history")
async def get_clipboard_history(limit: int = 10):
conn = sqlite3.connect("clipboard.db")
c = conn.cursor()
c.execute("SELECT content FROM clips ORDER BY created_at DESC LIMIT ?", (limit,))
rows = c.fetchall()
conn.close()
return {"history": [row[0] for row in rows]}
7. 安全注意事项
- 不要暴露到公网:这个工具没有强安全措施,仅限内网使用
- 定期清理上传文件:避免占用过多磁盘空间
- 敏感信息处理:不要在剪贴板中同步密码等敏感信息
- 使用HTTPS:如需在不可信网络使用,应该配置SSL证书
8. 性能优化技巧
- 对于大文件传输,可以添加进度显示:
javascript复制// 前端修改
const res = await fetch('/upload', {
method: 'POST',
body: formData
});
const reader = res.body.getReader();
while(true) {
const {done, value} = await reader.read();
if(done) break;
// 更新进度条
}
- 启用Gzip压缩减少传输量:
python复制from fastapi.middleware.gzip import GZipMiddleware
app.add_middleware(GZipMiddleware)
- 对于高并发场景,可以考虑:
python复制uvicorn.run(
app,
host="0.0.0.0",
port=8000,
workers=4, # 根据CPU核心数调整
limit_concurrency=100
)
这个局域网文件传输工具虽然简单,但解决了日常工作中的实际痛点。通过FastAPI,我们用不到100行代码就实现了一个功能完备的解决方案,这正是Python生态的魅力所在。根据实际需求,你可以继续扩展功能,比如添加文件分享链接、设置过期时间等,打造属于你自己的专属工具链。
