Files
zTools2/app/dao/uploaded_file_dao.py
zikai 09705a8843 feat: SFTP 上传的文件经磁盘扫描补录后可在文件浏览页展示
SFTP 上传的文件直接落盘到 upload_root(chroot 根),不经 HTTP 路径,无 DB 记录,
文件浏览页(读 uploaded_file 表)看不到。新增磁盘扫描机制:

- UploadService.scan_sftp_files(force=False):遍历 uploads/ 下所有文件(排除 .work/
  与 .part 残品),与 DB 已有 storage_path 比对,磁盘有 DB 无的补录(source="sftp",
  流式算 size + sha256)。
- 频率限制:模块级 _last_scan_time,两次扫描间隔 < 3s 则跳过返回 0(只读 DB 缓存),
  force=True 强制扫。
- file_admin_controller 的 list 接口返回前触发 scan_sftp_files(),文件浏览页打开/
  刷新即自动同步 SFTP 文件(受 3s 频率限制,不会每次翻页都扫磁盘)。
- UploadedFileDAO 加 list_storage_paths() 批量查已记录路径集合供比对。

SFTP 文件补录后可正常下载/删除(与 HTTP 上传文件一致)。
2026-07-22 01:25:41 +00:00

53 lines
1.6 KiB
Python

"""UploadedFile 的 DAO。"""
from __future__ import annotations
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from ..models.uploaded_file import UploadedFile
class UploadedFileDAO:
def __init__(self, db: Session) -> None:
self.db = db
def create(self, file: UploadedFile) -> UploadedFile:
self.db.add(file)
self.db.commit()
self.db.refresh(file)
return file
def get_by_id(self, file_id: int) -> UploadedFile | None:
return self.db.get(UploadedFile, file_id)
def get_by_sha256(self, sha256: str) -> UploadedFile | None:
stmt = select(UploadedFile).where(UploadedFile.sha256 == sha256).limit(1)
return self.db.scalars(stmt).first()
def count(self) -> int:
"""返回数据库中文件总条数。"""
return self.db.scalar(select(func.count()).select_from(UploadedFile)) or 0
def list_storage_paths(self) -> set[str]:
"""返回所有已记录的 storage_path 集合(供磁盘扫描比对,识别未入库文件)。"""
stmt = select(UploadedFile.storage_path)
return {row for row in self.db.scalars(stmt).all()}
def list(self, limit: int = 100, offset: int = 0) -> list[UploadedFile]:
stmt = (
select(UploadedFile)
.order_by(UploadedFile.uploaded_at.desc())
.limit(limit)
.offset(offset)
)
return list(self.db.scalars(stmt).all())
def delete(self, file_id: int) -> bool:
file = self.get_by_id(file_id)
if file is None:
return False
self.db.delete(file)
self.db.commit()
return True