我给你我的代码,你在我代码上去优化吧,server:
import tornado.ioloop
import tornado.web
import threading
import time
import os
import logging
import asyncio
from concurrent.futures import ThreadPoolExecutor
from handlers import MainHandler, WSHandler, UpdatePoolHandler, FileListHandler, FileDownloadHandler, FilePreviewHandler, ManualScanHandler
from file_utils import scan_pools
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("ResourceManager")
# 自定义线程池(用于异步执行资源池扫描等阻塞操作)
executor = ThreadPoolExecutor(4) # 根据 CPU 核心数调整线程池大小
# 设置 asyncio 默认事件循环的执行器为线程池
asyncio.get_event_loop().set_default_executor(executor)
# 修复模板路径和路由配置
def make_app():
base_dir = os.path.dirname(os.path.abspath(__file__))
template_path = os.path.join(base_dir, "templates")
static_path = os.path.join(base_dir, "static")
return tornado.web.Application(
[
(r"/", MainHandler),
(r"/ws", WSHandler),
(r"/update_pool", UpdatePoolHandler),
(r"/files/(.+)", FileListHandler),
(r"/download", FileDownloadHandler),
(r"/preview", FilePreviewHandler),
(r"/scan", ManualScanHandler),
(r"/static/(.*)", tornado.web.StaticFileHandler, {"path": static_path}),
(r"/admin", MainHandler),
],
template_path=template_path,
cookie_secret="YOUR_SECRET_KEY",
login_url="/login",
debug=False, # 生产环境建议关闭 debug
max_buffer_size=1024 * 1024 * 1024, # 最大缓冲区大小(1GB)
chunked_write_timeout=60000, # 分块写入超时时间(毫秒)
idle_connection_timeout=60000, # 空闲连接超时时间
executor=executor # 使用自定义线程池
)
if __name__ == "__main__":
try:
# 初始化资源池扫描
scan_pools()
logger.info("资源池扫描初始化完成")
except Exception as e:
logger.error(f"资源池扫描初始化失败: {str(e)}")
# 创建并启动应用
app = make_app()
app.listen(8888)
logger.info("服务器运行中: http://localhost:8888")
try:
# 启动事件循环
tornado.ioloop.IOLoop.current().start()
except KeyboardInterrupt:
logger.info("\n服务器正在关闭...")
except Exception as e:
logger.error(f"服务器异常停止: {str(e)}")
finally:
# 清理资源(可选)
executor.shutdown(wait=True)
#预览视频下载和下载按钮下载速度大幅度提升,txt文本实现在线预览,word等文档预览会下载而不是显示不支持预览,本地速度快,跨设备比较慢
handlers:
import tornado.web
import tornado.websocket
import json
import os
import mimetypes
import urllib.parse
from config import RESOURCE_POOLS, save_config, ADMIN_IP, ADMIN_PASSWORD
from file_utils import get_file_list, scan_pools
import aiofiles
# 改进管理员检查
def is_admin(handler):
"""检查当前用户是否有管理员权限"""
client_ip = handler.request.remote_ip
# 支持IPv4和IPv6的本地地址
return client_ip in ["127.0.0.1", "::1", "localhost"] or \
client_ip == ADMIN_IP or \
handler.get_cookie("admin_auth") == ADMIN_PASSWORD
# handlers.py
# 手动资源扫描接口
class ManualScanHandler(tornado.web.RequestHandler):
async def get(self):
if not is_admin(self):
self.set_status(403)
self.write("权限不足")
return
# 使用线程池异步执行 scan_pools
await tornado.ioloop.IOLoop.current().run_in_executor(executor, scan_pools)
WSHandler.broadcast_update() # 通知前端更新
self.write("资源池已手动扫描完成")
# 主页面处理器
class MainHandler(tornado.web.RequestHandler):
def get(self):
try:
if self.request.path == "/admin":
if not is_admin(self):
self.redirect("/")
return
self.render("admin.html", pools=RESOURCE_POOLS)
else:
if is_admin(self):
self.render("admin.html", pools=RESOURCE_POOLS)
else:
self.render("user.html", pools=RESOURCE_POOLS)
except FileNotFoundError:
self.set_status(404)
self.render("404.html")
except Exception as e:
self.set_status(500)
self.render("500.html", error=str(e))
# WebSocket实时更新处理器
class WSHandler(tornado.websocket.WebSocketHandler):
clients = set()
def open(self):
self.clients.add(self)
self.send_update()
def on_message(self, message):
# 处理客户端消息
pass
def on_close(self):
self.clients.remove(self)
def send_update(self):
"""发送更新到所有连接的客户端"""
for client in self.clients:
client.write_message(json.dumps(RESOURCE_POOLS))
@classmethod
def broadcast_update(cls):
"""广播更新到所有客户端"""
for client in cls.clients:
client.write_message(json.dumps(RESOURCE_POOLS))
# 资源池更新处理器(仅管理员)
class UpdatePoolHandler(tornado.web.RequestHandler):
def post(self):
if not is_admin(self):
self.write({"status": "error", "message": "无权限执行此操作"})
return
data = json.loads(self.request.body)
pool_name = data.get("pool")
new_path = data.get("path")
if pool_name and new_path and pool_name in RESOURCE_POOLS:
# 验证路径有效性
if not os.path.exists(new_path) or not os.path.isdir(new_path):
self.write({"status": "error", "message": "路径不存在或不是目录"})
return
# 更新路径并扫描文件
RESOURCE_POOLS[pool_name]["path"] = new_path
try:
RESOURCE_POOLS[pool_name]["files"] = [
f for f in os.listdir(new_path)
if os.path.isfile(os.path.join(new_path, f))
]
except Exception as e:
RESOURCE_POOLS[pool_name]["files"] = [f"错误: {str(e)}"]
# 保存配置并广播更新
save_config()
WSHandler.broadcast_update()
self.write({"status": "success"})
else:
self.write({"status": "error", "message": "无效资源池或路径"})
# 文件列表处理器
class FileListHandler(tornado.web.RequestHandler):
async def get(self, pool_name):
if pool_name not in RESOURCE_POOLS:
self.set_status(404)
self.render("404.html")
return
# 获取文件列表数据
files = get_file_list(pool_name)
# 渲染模板
self.render("file_list.html",
pool_name=pool_name,
files=files,
pool_info=RESOURCE_POOLS[pool_name],
is_admin=is_admin(self))
# handlers.py
# 文件下载处理器
class FileDownloadHandler(tornado.web.RequestHandler):
async def get(self):
pool_name = self.get_argument("pool")
file_name = self.get_argument("file")
if pool_name not in RESOURCE_POOLS:
self.set_status(404)
self.render("404.html")
return
pool_path = RESOURCE_POOLS[pool_name]["path"]
file_path = os.path.join(pool_path, file_name)
# 防止路径遍历攻击
real_path = os.path.realpath(file_path)
if not real_path.startswith(os.path.realpath(pool_path)):
self.set_status(403)
self.write("禁止访问此文件路径")
return
if not os.path.exists(file_path):
self.set_status(404)
self.render("404.html")
return
file_size = os.path.getsize(file_path)
range_header = self.request.headers.get('Range')
start = 0
end = file_size - 1
if range_header:
self.set_status(206) # Partial Content
start_bytes, end_bytes = range_header.replace('bytes=', '').split('-')
start = int(start_bytes) if start_bytes else 0
end = int(end_bytes) if end_bytes else file_size - 1
self.set_header('Content-Range', f'bytes {start}-{end}/{file_size}')
self.set_header("Content-Length", str(end - start + 1))
encoded_filename = urllib.parse.quote(file_name.encode('utf-8'))
self.set_header("Content-Type", "application/octet-stream")
self.set_header("Content-Disposition", f'attachment; filename*=UTF-8\'\'{encoded_filename}')
self.set_header("Accept-Ranges", "bytes")
chunk_size = 1024 * 1024 * 4 # 4MB
try:
async with aiofiles.open(file_path, 'rb') as f:
await f.seek(start)
remaining = end - start + 1
while remaining > 0:
read_size = min(chunk_size, remaining)
chunk = await f.read(read_size)
if not chunk:
break
self.write(chunk)
await self.flush()
remaining -= len(chunk)
self.finish()
except tornado.iostream.StreamClosedError:
# 客户端提前关闭连接,静默处理
return
except Exception as e:
self.set_status(500)
self.write(f"Internal Server Error: {str(e)}")
return
class FilePreviewHandler(tornado.web.RequestHandler):
async def get(self):
pool_name = self.get_argument("pool")
file_name = self.get_argument("file")
if pool_name not in RESOURCE_POOLS:
self.set_status(404)
self.render("404.html")
return
pool_path = RESOURCE_POOLS[pool_name]["path"]
file_path = os.path.join(pool_path, file_name)
# 防止路径遍历攻击
real_path = os.path.realpath(file_path)
if not real_path.startswith(os.path.realpath(pool_path)):
self.set_status(403)
self.write("禁止访问此文件路径")
return
if not os.path.exists(file_path):
self.set_status(404)
self.render("404.html")
return
# 获取扩展名
ext = os.path.splitext(file_name)[1].lower()
# 手动定义 MIME 类型
mime_type = {
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'.pdf': 'application/pdf',
'.txt': 'text/plain',
'.md': 'text/markdown',
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.mp4': 'video/mp4',
'.webm': 'video/webm',
'.ogg': 'video/ogg',
}.get(ext, mimetypes.guess_type(file_name)[0] or 'application/octet-stream')
# 设置 UTF-8 编码的文件名
encoded_filename = urllib.parse.quote(file_name.encode('utf-8'))
self.set_header("Content-Disposition", f'inline; filename*=UTF-8\'\'{encoded_filename}')
file_size = os.path.getsize(file_path)
# 区分是否是 Range 请求(预览)还是完整下载
range_header = self.request.headers.get('Range')
is_download = not range_header and ext in ['.mp4', '.webm', '.ogg', '.pdf']
if is_download:
# 完整下载,使用 sendfile()(Linux) 或同步读取
self.set_header('Content-Type', mime_type)
self.set_header('Content-Length', str(file_size))
self.set_status(200)
if os.name == 'posix':
# Linux 下使用 sendfile 实现零拷贝传输
fd = os.open(file_path, os.O_RDONLY)
try:
await tornado.ioloop.IOLoop.current().run_in_executor(
None,
os.sendfile,
self.request.connection.fileno(),
fd,
None,
file_size
)
finally:
os.close(fd)
else:
# Windows 或其他系统,使用同步读取(比 aiofiles 更快)
with open(file_path, 'rb') as f:
while True:
chunk = f.read(1024 * 1024 * 4) # 4MB
if not chunk:
break
self.write(chunk)
await self.flush()
self.finish()
elif ext in ['.mp4', '.webm', '.ogg', '.pdf']:
# 视频预览,使用 aiofiles 支持 Range
self.set_header('Accept-Ranges', 'bytes')
start = 0
end = file_size - 1
if range_header:
self.set_status(206)
start_bytes, end_bytes = range_header.replace('bytes=', '').split('-')
start = int(start_bytes) if start_bytes else 0
end = int(end_bytes) if end_bytes else file_size - 1
self.set_header('Content-Range', f'bytes {start}-{end}/{file_size}')
self.set_header("Content-Length", str(end - start + 1))
self.set_header('Content-Type', mime_type)
chunk_size = 4096 * 16
async with aiofiles.open(file_path, 'rb') as f:
await f.seek(start)
remaining = end - start + 1
while remaining > 0:
chunk = await f.read(min(chunk_size, remaining))
if not chunk:
break
self.write(chunk)
await self.flush()
remaining -= len(chunk)
self.finish()
elif ext == '.txt':
self.set_header('Content-Type', 'text/plain; charset=UTF-8')
async with aiofiles.open(file_path, 'r', encoding='utf-8') as f:
content = await f.read()
self.write(content)
self.finish()
elif ext in ['.png', '.jpg', '.jpeg', '.gif', '.md', '.pdf', '.docx', '.xlsx', '.pptx']:
self.set_header('Content-Type', mime_type)
async with aiofiles.open(file_path, 'rb') as f:
data = await f.read()
self.write(data)
self.finish()
else:
self.set_status(400)
self.write("不支持预览此文件类型")