2 Commits
4 changed files with 33 additions and 7 deletions
+1 -5
View File
@@ -587,14 +587,10 @@ class TaskManager:
return _next_run_time(job.schedule) return _next_run_time(job.schedule)
def run_job_now(self, job_id): def run_job_now(self, job_id):
"""立即执行任务(手动触发)。运行窗口外拒绝启动。""" """立即执行任务(手动触发)。运行窗口只约束定时触发,手动执行不受限制。"""
job = self.jobs.get(job_id) job = self.jobs.get(job_id)
if not job: if not job:
return {"ok": False, "error": "任务不存在"} return {"ok": False, "error": "任务不存在"}
if not _in_run_window(job.schedule):
win = job.schedule.get("window") or {}
return {"ok": False,
"error": f"当前不在运行窗口内({win.get('start', '')}-{win.get('end', '')}),任务未启动"}
# 在独立线程跑,不阻塞调用方 # 在独立线程跑,不阻塞调用方
t = threading.Thread(target=self._run_job, args=(job,), daemon=True) t = threading.Thread(target=self._run_job, args=(job,), daemon=True)
t.start() t.start()
+30
View File
@@ -16,4 +16,34 @@ fi
pip uninstall -y -q opencv-python 2>/dev/null || true pip uninstall -y -q opencv-python 2>/dev/null || true
pip install -q -i https://pypi.tuna.tsinghua.edu.cn/simple "opencv-python-headless>=4.8,<5" # 5.x wheel 无 cv2 模块,锁定 4.x pip install -q -i https://pypi.tuna.tsinghua.edu.cn/simple "opencv-python-headless>=4.8,<5" # 5.x wheel 无 cv2 模块,锁定 4.x
# pip 24.0 解压 abi3 wheel 偶发丢根文件(4.14.0.94 实测 site-packages/cv2 只剩子目录,
# cv2.abi3.so / __init__.py 缺失 → import cv2 成空壳,OCR 全挂)→ 空壳检测 + wheel 手动解压兜底(幂等)
python3 - <<'PY'
import glob, os, shutil, site, subprocess, sys, zipfile
def cv2_ok():
try:
import cv2
return hasattr(cv2, "__version__") and cv2.__file__ is not None
except Exception:
return False
if cv2_ok():
sys.exit(0)
print("cv2 空壳,wheel 手动解压修复...")
r = subprocess.run(["pip", "download", "--no-deps", "-q",
"-i", "https://pypi.tuna.tsinghua.edu.cn/simple",
"-d", "/tmp/ocv", "opencv-python-headless>=4.8,<5"], capture_output=True)
if r.returncode != 0:
sys.exit(1)
w = sorted(glob.glob("/tmp/ocv/opencv_python_headless*.whl"))[-1]
with zipfile.ZipFile(w) as z:
z.extractall("/tmp/ocv")
dst = os.path.join(site.getsitepackages()[0], "cv2")
shutil.rmtree(dst, ignore_errors=True)
shutil.copytree("/tmp/ocv/cv2", dst)
import cv2
print(f"cv2 {cv2.__version__} 手动解压修复完成")
PY
exec python -u web_server.py exec python -u web_server.py
+1 -1
View File
@@ -1082,7 +1082,7 @@ async function saveTask(jobId){
cron:buildCronFromFreq(), cron:buildCronFromFreq(),
stop_cron:(parseInt(st[1])||0)+' '+(parseInt(st[0])||0)+' * * *'}; stop_cron:(parseInt(st[1])||0)+' '+(parseInt(st[0])||0)+' * * *'};
} }
// 运行窗口:窗口外(定时触发 + 手动执行)不启动 // 运行窗口:只约束定时触发(窗口外定时不启动),手动执行不受限制
if(document.getElementById('f-window_enable').checked){ if(document.getElementById('f-window_enable').checked){
schedule.window={ schedule.window={
start:document.getElementById('f-window_start').value||'09:00', start:document.getElementById('f-window_start').value||'09:00',
+1 -1
View File
@@ -141,7 +141,7 @@ function openTaskModal(jobId){
'<div class="form-group"><label>允许运行·开始</label><input type="time" class="form-control" id="f-window_start" value="'+esc(winStart)+'"></div>'+ '<div class="form-group"><label>允许运行·开始</label><input type="time" class="form-control" id="f-window_start" value="'+esc(winStart)+'"></div>'+
'<div class="form-group"><label>允许运行·结束</label><input type="time" class="form-control" id="f-window_end" value="'+esc(winEnd)+'"></div>'+ '<div class="form-group"><label>允许运行·结束</label><input type="time" class="form-control" id="f-window_end" value="'+esc(winEnd)+'"></div>'+
'</div>'+ '</div>'+
'<div class="help" id="f-window_help" style="display:'+(win?'block':'none')+'">窗口外任务不会启动(定时触发和手动执行都受约束)。跨午夜示例:21:00-09:00 = 晚 9 点到次日早 9 点允许运行</div>'+ '<div class="help" id="f-window_help" style="display:'+(win?'block':'none')+'">窗口只约束定时触发:窗口外定时不会启动,手动执行不受限制。跨午夜示例:21:00-09:00 = 晚 9 点到次日早 9 点允许运行</div>'+
'<div class="form-row">'+ '<div class="form-row">'+
'<div class="form-group"><label>重试次数</label><input type="number" class="form-control" id="f-retry_attempts" value="'+j.retry.max_attempts+'" min="1" max="10"></div>'+ '<div class="form-group"><label>重试次数</label><input type="number" class="form-control" id="f-retry_attempts" value="'+j.retry.max_attempts+'" min="1" max="10"></div>'+
'<div class="form-group"><label>重试间隔(秒)</label><input type="number" class="form-control" id="f-retry_delay" value="'+j.retry.delay+'" min="10" max="3600"></div>'+ '<div class="form-group"><label>重试间隔(秒)</label><input type="number" class="form-control" id="f-retry_delay" value="'+j.retry.delay+'" min="10" max="3600"></div>'+