Files
zTools2/app/services/tunnel_service.py
zikai fffba79022 feat: 文件浏览页 + 共享白板 + 白板管理页(含补登记分片上传/隧道历史改动)
本次提交包含两批改动(7月2日遗留未入库 + 本次新功能),分述如下:

【补登记:7月2日已上线但未提交的功能】
- 分片上传:chunk_upload_controller/service/dao + UploadSession model/schema,
  支持 4MiB 分片、乱序、断点续传、去重、幂等 complete;后台 reaper 清理过期会话。
- 反向隧道:tunnel_controller/service/dao + TunnelSession model/schema,
  SSH remote forward 经 /api/userPort/{userName} 反代到 user 本地服务。
- 上传页:views/upload_html.py(拖拽/多文件/分片/断点续传 UI)。
- config.py:StorageConfig.chunk_session_dir/ttl、TunnelConfig;
  requirements.txt 加 httpx;start.sh 清理 .work/ 残留;
  schema.sql 加 upload_session/tunnel_session 表;sftp_server 承载隧道转发。

【本次新功能】
- 文件浏览页:GET /files(Basic Auth 同 docs)+ /api/admin/files(list/get/download/DELETE)。
  硬删除(DB 行 + 磁盘文件),删除后列表不再显示。前端 static/file_browser.*。
- 共享白板:GET /whiteboard/{id}(公开,不存在则新建)+ WS /ws/whiteboard/{id}。
  MySQL 持久化(whiteboard 表),Canvas 实时同步,心跳 3s/5 次失活移除,
  清空/复制按钮,移动端兼容。WhiteboardHub 管理 {board_id: set[Connection]},
  disconnect 幂等 + 空 set 清理防泄漏,broadcast 失败连接自动移除。
- 白板管理页:GET /whiteboard-admin(Basic Auth)+ /api/admin/whiteboards(list/DELETE)。
  删除时 hub.close_board 踢出在线连接。
- 清理:合并 UploadService.get_out_with_disk_path(下载/删除复用,消除重复 DB 读),
  移除无用 resolve_disk_path。
- config.py:WhiteboardConfig(heartbeat/threshold/board_id 长度/list_limit);
  schema.sql 加 whiteboard 表;README 补新接口与心跳/内存说明。
- 验证:tests/manual_whiteboard_hub.py / _ws.py / _kick.py 全部通过。
2026-07-21 14:28:13 +00:00

71 lines
2.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""反向隧道业务逻辑:会话注册 / 注销 / 查询。
SSH 服务收到 remote port-forward 请求时调 register 记一条 active 会话;
user 断开时调 close 标记 ended_atHTTP 路由调 get_active 查隧道端口做反代。
"""
from __future__ import annotations
import logging
from ..config import get_settings
from ..dao.tunnel_session_dao import TunnelSessionDAO
from ..models.tunnel_session import TunnelSession
logger = logging.getLogger("zikai.tunnel")
class TunnelService:
def __init__(self, dao: TunnelSessionDAO) -> None:
self.dao = dao
self.settings = get_settings().tunnel
def register(
self, user_name: str, user_ip: str, tunnel_port: int, local_port: int,
) -> TunnelSession:
"""登记一条活跃隧道会话。
若该 user 已有活跃会话先关闭旧的(同一 user 同一时刻只保留一条)。
"""
self.dao.close_active_by_user(user_name)
session = TunnelSession(
user_name=user_name,
user_ip=user_ip,
local_port=local_port,
tunnel_port=tunnel_port,
status="active",
)
saved = self.dao.create(session)
logger.info(
"隧道建立 user=%s ip=%s tunnel_port=%d local_port=%d",
user_name, user_ip, tunnel_port, local_port,
)
return saved
def close(self, user_name: str) -> int:
"""关闭该 user 的活跃会话SSH 断开时调用),返回关闭条数。"""
n = self.dao.close_active_by_user(user_name)
if n:
logger.info("隧道关闭 user=%s 条数=%d", user_name, n)
return n
def get_active(self, user_name: str) -> TunnelSession | None:
return self.dao.get_active_by_user(user_name)
def is_port_allowed(self, user_name: str, tunnel_port: int) -> bool:
"""校验该 user 是否被允许绑定该隧道端口(防 user 乱绑端口)。"""
user = self.settings.find_user(user_name)
return user is not None and user.tunnel_port == tunnel_port
def reap_orphans(self) -> int:
"""兜底清理:关闭所有 active 会话(进程重启时 DB 里残留的孤儿记录)。
由后台 reaper 在启动后调用一次。SSH 实际断开时已有 close() 处理,
这里只兜底进程异常退出后 DB 与实际状态不一致的情况。
"""
active = self.dao.list_active()
for session in active:
self.dao.close(session)
logger.info("reaper 清理孤儿隧道 id=%d user=%s", session.id, session.user_name)
return len(active)