fix: 跳过自动调度空目录
This commit is contained in:
@@ -238,6 +238,8 @@ Server Portable 和桌面版需要在目标系统原生构建:Windows 包在 W
|
|||||||
|
|
||||||
自动调度开启后,系统会强制开启 `RemoteData.enabled` 和 `auto_delete_source`。调度器每轮先检查 `cache/auto_scheduler/ready.flag`;如果标识存在则直接触发远程下载并处理。没有标识时,会按 ZIP 文件名中的第一个时间戳判断每个目录是否覆盖目标自然周 7 天;全部就绪后写入标识,下一个检查周期再启动处理。处理成功并完成远程源文件清理后会删除标识;处理失败或源文件清理失败会保留标识,后续自动重试。
|
自动调度开启后,系统会强制开启 `RemoteData.enabled` 和 `auto_delete_source`。调度器每轮先检查 `cache/auto_scheduler/ready.flag`;如果标识存在则直接触发远程下载并处理。没有标识时,会按 ZIP 文件名中的第一个时间戳判断每个目录是否覆盖目标自然周 7 天;全部就绪后写入标识,下一个检查周期再启动处理。处理成功并完成远程源文件清理后会删除标识;处理失败或源文件清理失败会保留标识,后续自动重试。
|
||||||
|
|
||||||
|
如果配置了 `expected_directories`,其中某个目录可访问但完全没有 ZIP 文件,会视为该目录已停推并跳过,不再阻塞其他目录;但所有目录都为空时不会触发自动处理。
|
||||||
|
|
||||||
无论手动上传还是远程下载,处理流程都会按文件名日期对每个目录只保留最近 7 天文件。文件名支持 `XXX_YYYYMMDDHHMM_YYYYMMDDHHMM` 和 `XXX_YYYYMMDDHHMM` 两类格式,数据日期始终取第一个时间戳。
|
无论手动上传还是远程下载,处理流程都会按文件名日期对每个目录只保留最近 7 天文件。文件名支持 `XXX_YYYYMMDDHHMM_YYYYMMDDHHMM` 和 `XXX_YYYYMMDDHHMM` 两类格式,数据日期始终取第一个时间戳。
|
||||||
|
|
||||||
登录密码保存在本地 `auth.ini`,该文件不应提交到版本库。
|
登录密码保存在本地 `auth.ini`,该文件不应提交到版本库。
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ class DirectoryReadyStatus:
|
|||||||
missing_days: list[date]
|
missing_days: list[date]
|
||||||
file_count: int
|
file_count: int
|
||||||
error: str | None = None
|
error: str | None = None
|
||||||
|
skipped: bool = False
|
||||||
|
skip_reason: str | None = None
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
@@ -40,6 +42,8 @@ class DirectoryReadyStatus:
|
|||||||
"required_count": len(self.found_days) + len(self.missing_days),
|
"required_count": len(self.found_days) + len(self.missing_days),
|
||||||
"file_count": self.file_count,
|
"file_count": self.file_count,
|
||||||
"error": self.error,
|
"error": self.error,
|
||||||
|
"skipped": self.skipped,
|
||||||
|
"skip_reason": self.skip_reason,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -145,7 +149,6 @@ class AutoScheduler:
|
|||||||
|
|
||||||
target_days = required_week_days(scheduler.week_offset)
|
target_days = required_week_days(scheduler.week_offset)
|
||||||
directory_status = self._check_remote_ready(remote_config, scheduler, target_days)
|
directory_status = self._check_remote_ready(remote_config, scheduler, target_days)
|
||||||
ready = bool(directory_status) and all(item.ready for item in directory_status.values())
|
|
||||||
self._set_directory_status(directory_status)
|
self._set_directory_status(directory_status)
|
||||||
|
|
||||||
error_count = sum(1 for item in directory_status.values() if item.error)
|
error_count = sum(1 for item in directory_status.values() if item.error)
|
||||||
@@ -156,18 +159,30 @@ class AutoScheduler:
|
|||||||
manual,
|
manual,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
active_status = [item for item in directory_status.values() if not item.skipped]
|
||||||
|
skipped_count = len(directory_status) - len(active_status)
|
||||||
|
ready = bool(active_status) and all(item.ready for item in active_status)
|
||||||
if ready:
|
if ready:
|
||||||
self._mark_ready(target_days, directory_status)
|
self._mark_ready(target_days, directory_status)
|
||||||
|
skipped_text = f",已跳过 {skipped_count} 个停推目录" if skipped_count else ""
|
||||||
return self._finish_check(
|
return self._finish_check(
|
||||||
"marked_ready",
|
"marked_ready",
|
||||||
"远程数据已满足目标周 7 天,已写入就绪标识,下次检查将自动处理",
|
f"远程数据已满足目标周 7 天{skipped_text},已写入就绪标识,下次检查将自动处理",
|
||||||
manual,
|
manual,
|
||||||
)
|
)
|
||||||
|
|
||||||
ready_count = sum(1 for item in directory_status.values() if item.ready)
|
if not active_status and directory_status:
|
||||||
|
return self._finish_check(
|
||||||
|
"waiting",
|
||||||
|
f"远程数据未就绪,{len(directory_status)} 个目录均为空,已视为停推但不会触发处理",
|
||||||
|
manual,
|
||||||
|
)
|
||||||
|
|
||||||
|
ready_count = sum(1 for item in active_status if item.ready)
|
||||||
|
skipped_text = f",跳过 {skipped_count} 个停推目录" if skipped_count else ""
|
||||||
return self._finish_check(
|
return self._finish_check(
|
||||||
"waiting",
|
"waiting",
|
||||||
f"远程数据未就绪,{ready_count}/{len(directory_status)} 个目录满足目标周 7 天",
|
f"远程数据未就绪,{ready_count}/{len(active_status)} 个有效目录满足目标周 7 天{skipped_text}",
|
||||||
manual,
|
manual,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -249,6 +264,17 @@ class AutoScheduler:
|
|||||||
error="远程目录不存在或无法访问",
|
error="远程目录不存在或无法访问",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not files:
|
||||||
|
return DirectoryReadyStatus(
|
||||||
|
directory=directory,
|
||||||
|
ready=True,
|
||||||
|
found_days=[],
|
||||||
|
missing_days=[],
|
||||||
|
file_count=0,
|
||||||
|
skipped=True,
|
||||||
|
skip_reason="目录为空,视为已停推并跳过",
|
||||||
|
)
|
||||||
|
|
||||||
required = set(target_days)
|
required = set(target_days)
|
||||||
found = {
|
found = {
|
||||||
file_date
|
file_date
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ def get_target_week_range(week_offset: int = 0) -> tuple[date, date]:
|
|||||||
1. **自动调度开启时,`auto_delete_source` 强制为 `true`**(前端灰显、后端校验)
|
1. **自动调度开启时,`auto_delete_source` 强制为 `true`**(前端灰显、后端校验)
|
||||||
2. `expected_directories` 为空时按远程 ZIP 实际所在目录逐个检测
|
2. `expected_directories` 为空时按远程 ZIP 实际所在目录逐个检测
|
||||||
3. `check_interval_hours` 最小值为 1
|
3. `check_interval_hours` 最小值为 1
|
||||||
|
4. 配置了 `expected_directories` 时,某个预期目录可访问但完全没有 ZIP 文件,视为该目录已停推并跳过;所有目录都为空时不触发自动处理
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -308,7 +309,7 @@ GET /api/remote/scheduler/status
|
|||||||
| 连续失败 ≥3 次 | 在前端调度状态面板显示红色失败状态 |
|
| 连续失败 ≥3 次 | 在前端调度状态面板显示红色失败状态 |
|
||||||
| 处理失败 | **保留就绪标识**,下次检查时直接触发处理(不重新检测) |
|
| 处理失败 | **保留就绪标识**,下次检查时直接触发处理(不重新检测) |
|
||||||
| 远程目录不存在 | 跳过该目录,记录警告,其他目录正常检测 |
|
| 远程目录不存在 | 跳过该目录,记录警告,其他目录正常检测 |
|
||||||
| 目录内无 ZIP 文件 | 该目录判定为未就绪 |
|
| 预期目录内无 ZIP 文件 | 视为该目录已停推并跳过,不阻塞其他目录;如果所有目录都为空,则继续等待,不触发处理 |
|
||||||
| 手动触发远程处理 | 自动调度运行中禁止手动触发,反之亦然(互斥锁复用 `global_task_lock`) |
|
| 手动触发远程处理 | 自动调度运行中禁止手动触发,反之亦然(互斥锁复用 `global_task_lock`) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
- `app/api/routers/remote.py` 新增 `/api/remote/scheduler/status` 和 `/api/remote/scheduler/trigger`,并将远程处理启动逻辑抽成 `start_remote_processing_job()` 供手动按钮和调度器复用。
|
- `app/api/routers/remote.py` 新增 `/api/remote/scheduler/status` 和 `/api/remote/scheduler/trigger`,并将远程处理启动逻辑抽成 `start_remote_processing_job()` 供手动按钮和调度器复用。
|
||||||
- `frontend/src/components/SettingsPanel.vue` 在远程数据源配置中加入自动调度区域:启用开关、检查间隔、目标周期、预期目录维护、调度状态、刷新状态和立即检查。配置下载/上传会随 `RemoteData` 一起携带自动调度配置。
|
- `frontend/src/components/SettingsPanel.vue` 在远程数据源配置中加入自动调度区域:启用开关、检查间隔、目标周期、预期目录维护、调度状态、刷新状态和立即检查。配置下载/上传会随 `RemoteData` 一起携带自动调度配置。
|
||||||
- 已验证:文件名日期解析、目标周计算、每目录 7 天筛选、调度开启强制删除源文件配置、调度日期精确筛选逻辑均通过临时 Python 片段;`.venv\Scripts\python.exe -m compileall app` 与 `npm run build` 通过,前端构建仅保留既有 Vite 大 chunk 警告。
|
- 已验证:文件名日期解析、目标周计算、每目录 7 天筛选、调度开启强制删除源文件配置、调度日期精确筛选逻辑均通过临时 Python 片段;`.venv\Scripts\python.exe -m compileall app` 与 `npm run build` 通过,前端构建仅保留既有 Vite 大 chunk 警告。
|
||||||
|
- 后续调整:预期目录可访问但完全没有 ZIP 文件时,自动调度会把该目录标记为 `skipped/已停推`,不再阻塞其他目录就绪;但所有目录都为空时不会写入 ready flag,也不会触发处理。
|
||||||
|
|
||||||
## 2026-05-25:修复 API Token 指定日期输入不可见
|
## 2026-05-25:修复 API Token 指定日期输入不可见
|
||||||
|
|
||||||
|
|||||||
@@ -230,8 +230,8 @@
|
|||||||
<div v-if="schedulerDirectoryRows.length > 0" class="scheduler-directory-list">
|
<div v-if="schedulerDirectoryRows.length > 0" class="scheduler-directory-list">
|
||||||
<div v-for="row in schedulerDirectoryRows" :key="row.name" class="scheduler-directory-row">
|
<div v-for="row in schedulerDirectoryRows" :key="row.name" class="scheduler-directory-row">
|
||||||
<span class="scheduler-directory-name">{{ row.name }}</span>
|
<span class="scheduler-directory-name">{{ row.name }}</span>
|
||||||
<n-tag size="small" :type="row.ready ? 'success' : 'warning'">
|
<n-tag size="small" :type="row.skipped ? 'info' : row.ready ? 'success' : 'warning'">
|
||||||
{{ row.ready ? '就绪' : `缺 ${row.missing_days.length} 天` }}
|
{{ row.skipped ? '已停推' : row.ready ? '就绪' : `缺 ${row.missing_days.length} 天` }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ export interface RemoteSchedulerStatus {
|
|||||||
required_count: number;
|
required_count: number;
|
||||||
file_count: number;
|
file_count: number;
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
|
skipped?: boolean;
|
||||||
|
skip_reason?: string | null;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user