refactor: optimize structure, stability and runtime performance
This commit is contained in:
@@ -243,6 +243,35 @@ class KDocsUploader:
|
||||
except queue.Empty:
|
||||
return {"success": False, "error": "操作超时"}
|
||||
|
||||
def _put_task_response(self, task: Dict[str, Any], result: Dict[str, Any]) -> None:
|
||||
response_queue = task.get("response")
|
||||
if not response_queue:
|
||||
return
|
||||
try:
|
||||
response_queue.put(result)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
def _process_task(self, task: Dict[str, Any]) -> bool:
|
||||
action = task.get("action")
|
||||
payload = task.get("payload") or {}
|
||||
|
||||
if action == "shutdown":
|
||||
return False
|
||||
if action == "upload":
|
||||
self._handle_upload(payload)
|
||||
return True
|
||||
if action == "qr":
|
||||
self._put_task_response(task, self._handle_qr(payload))
|
||||
return True
|
||||
if action == "clear_login":
|
||||
self._put_task_response(task, self._handle_clear_login())
|
||||
return True
|
||||
if action == "status":
|
||||
self._put_task_response(task, self._handle_status_check())
|
||||
return True
|
||||
return True
|
||||
|
||||
def _run(self) -> None:
|
||||
thread_id = self._thread_id
|
||||
logger.info(f"[KDocs] 上传线程启动 (ID={thread_id})")
|
||||
@@ -261,34 +290,17 @@ class KDocsUploader:
|
||||
# 更新最后活动时间
|
||||
self._last_activity = time.time()
|
||||
|
||||
action = task.get("action")
|
||||
if action == "shutdown":
|
||||
break
|
||||
|
||||
try:
|
||||
if action == "upload":
|
||||
self._handle_upload(task.get("payload") or {})
|
||||
elif action == "qr":
|
||||
result = self._handle_qr(task.get("payload") or {})
|
||||
task.get("response").put(result)
|
||||
elif action == "clear_login":
|
||||
result = self._handle_clear_login()
|
||||
task.get("response").put(result)
|
||||
elif action == "status":
|
||||
result = self._handle_status_check()
|
||||
task.get("response").put(result)
|
||||
should_continue = self._process_task(task)
|
||||
if not should_continue:
|
||||
break
|
||||
|
||||
# 任务处理完成后更新活动时间
|
||||
self._last_activity = time.time()
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[KDocs] 处理任务失败: {e}")
|
||||
# 如果有响应队列,返回错误
|
||||
if "response" in task and task.get("response"):
|
||||
try:
|
||||
task["response"].put({"success": False, "error": str(e)})
|
||||
except Exception:
|
||||
pass
|
||||
self._put_task_response(task, {"success": False, "error": str(e)})
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[KDocs] 线程主循环异常: {e}")
|
||||
@@ -830,18 +842,180 @@ class KDocsUploader:
|
||||
except Exception as e:
|
||||
logger.warning(f"[KDocs] 保存登录态失败: {e}")
|
||||
|
||||
def _resolve_doc_url(self, cfg: Dict[str, Any]) -> str:
|
||||
return (cfg.get("kdocs_doc_url") or "").strip()
|
||||
|
||||
def _ensure_doc_access(
|
||||
self,
|
||||
doc_url: str,
|
||||
*,
|
||||
fast: bool = False,
|
||||
use_storage_state: bool = True,
|
||||
) -> Optional[str]:
|
||||
if not self._ensure_playwright(use_storage_state=use_storage_state):
|
||||
return self._last_error or "浏览器不可用"
|
||||
if not self._open_document(doc_url, fast=fast):
|
||||
return self._last_error or "打开文档失败"
|
||||
return None
|
||||
|
||||
def _trigger_fast_login_dialog(self, timeout_ms: int) -> None:
|
||||
self._ensure_login_dialog(
|
||||
timeout_ms=timeout_ms,
|
||||
frame_timeout_ms=timeout_ms,
|
||||
quick=True,
|
||||
)
|
||||
|
||||
def _capture_qr_with_retry(self, fast_login_timeout: int) -> Tuple[Optional[bytes], Optional[bytes]]:
|
||||
qr_image = None
|
||||
invalid_qr = None
|
||||
for attempt in range(10):
|
||||
if attempt in (3, 7):
|
||||
self._trigger_fast_login_dialog(fast_login_timeout)
|
||||
candidate = self._capture_qr_image()
|
||||
if candidate and self._is_valid_qr_image(candidate):
|
||||
qr_image = candidate
|
||||
break
|
||||
if candidate:
|
||||
invalid_qr = candidate
|
||||
time.sleep(0.8) # 优化: 1 -> 0.8
|
||||
return qr_image, invalid_qr
|
||||
|
||||
def _save_qr_debug_artifacts(self, invalid_qr: Optional[bytes]) -> None:
|
||||
try:
|
||||
pages = self._iter_pages()
|
||||
page_urls = [getattr(p, "url", "") for p in pages]
|
||||
logger.warning(f"[KDocs] 二维码未捕获,页面: {page_urls}")
|
||||
|
||||
ts = int(time.time())
|
||||
saved = []
|
||||
for idx, page in enumerate(pages[:3]):
|
||||
try:
|
||||
path = f"data/kdocs_debug_{ts}_{idx}.png"
|
||||
page.screenshot(path=path, full_page=True)
|
||||
saved.append(path)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if saved:
|
||||
logger.warning(f"[KDocs] 已保存调试截图: {saved}")
|
||||
|
||||
if invalid_qr:
|
||||
try:
|
||||
path = f"data/kdocs_invalid_qr_{ts}.png"
|
||||
with open(path, "wb") as handle:
|
||||
handle.write(invalid_qr)
|
||||
logger.warning(f"[KDocs] 已保存无效二维码截图: {path}")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _log_upload_failure(self, message: str, user_id: Any, account_id: Any) -> None:
|
||||
try:
|
||||
log_to_client(f"表格上传失败: {message}", user_id, account_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _mark_upload_tracking(self, user_id: Any, account_id: Any) -> Tuple[Any, Optional[str], bool]:
|
||||
account = None
|
||||
prev_status = None
|
||||
status_tracked = False
|
||||
try:
|
||||
account = safe_get_account(user_id, account_id)
|
||||
if account and self._should_mark_upload(account):
|
||||
prev_status = getattr(account, "status", None)
|
||||
account.status = "上传截图"
|
||||
self._emit_account_update(user_id, account)
|
||||
status_tracked = True
|
||||
except Exception:
|
||||
prev_status = None
|
||||
return account, prev_status, status_tracked
|
||||
|
||||
def _parse_upload_payload(self, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
unit = (payload.get("unit") or "").strip()
|
||||
name = (payload.get("name") or "").strip()
|
||||
image_path = payload.get("image_path")
|
||||
|
||||
if not unit or not name:
|
||||
return None
|
||||
if not image_path or not os.path.exists(image_path):
|
||||
return None
|
||||
|
||||
return {
|
||||
"unit": unit,
|
||||
"name": name,
|
||||
"image_path": image_path,
|
||||
"user_id": payload.get("user_id"),
|
||||
"account_id": payload.get("account_id"),
|
||||
}
|
||||
|
||||
def _resolve_upload_sheet_config(self, cfg: Dict[str, Any]) -> Dict[str, Any]:
|
||||
return {
|
||||
"sheet_name": (cfg.get("kdocs_sheet_name") or "").strip(),
|
||||
"sheet_index": int(cfg.get("kdocs_sheet_index") or 0),
|
||||
"unit_col": (cfg.get("kdocs_unit_column") or "A").strip().upper(),
|
||||
"image_col": (cfg.get("kdocs_image_column") or "D").strip().upper(),
|
||||
"row_start": int(cfg.get("kdocs_row_start") or 0),
|
||||
"row_end": int(cfg.get("kdocs_row_end") or 0),
|
||||
}
|
||||
|
||||
def _try_upload_to_sheet(self, cfg: Dict[str, Any], unit: str, name: str, image_path: str) -> Tuple[bool, str]:
|
||||
sheet_cfg = self._resolve_upload_sheet_config(cfg)
|
||||
success = False
|
||||
error_msg = ""
|
||||
|
||||
for _ in range(2):
|
||||
try:
|
||||
if sheet_cfg["sheet_name"] or sheet_cfg["sheet_index"]:
|
||||
self._select_sheet(sheet_cfg["sheet_name"], sheet_cfg["sheet_index"])
|
||||
|
||||
row_num = self._find_person_with_unit(
|
||||
unit,
|
||||
name,
|
||||
sheet_cfg["unit_col"],
|
||||
row_start=sheet_cfg["row_start"],
|
||||
row_end=sheet_cfg["row_end"],
|
||||
)
|
||||
if row_num < 0:
|
||||
error_msg = f"未找到人员: {unit}-{name}"
|
||||
break
|
||||
|
||||
success = self._upload_image_to_cell(row_num, image_path, sheet_cfg["image_col"])
|
||||
if success:
|
||||
break
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
|
||||
return success, error_msg
|
||||
|
||||
def _handle_upload_login_invalid(
|
||||
self,
|
||||
*,
|
||||
unit: str,
|
||||
name: str,
|
||||
image_path: str,
|
||||
user_id: Any,
|
||||
account_id: Any,
|
||||
) -> None:
|
||||
error_msg = "登录已失效,请管理员重新扫码登录"
|
||||
self._login_required = True
|
||||
self._last_login_ok = False
|
||||
self._notify_admin(unit, name, image_path, error_msg)
|
||||
self._log_upload_failure(error_msg, user_id, account_id)
|
||||
|
||||
def _handle_qr(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
cfg = self._load_system_config()
|
||||
doc_url = (cfg.get("kdocs_doc_url") or "").strip()
|
||||
doc_url = self._resolve_doc_url(cfg)
|
||||
if not doc_url:
|
||||
return {"success": False, "error": "未配置金山文档链接"}
|
||||
|
||||
force = bool(payload.get("force"))
|
||||
if force:
|
||||
self._handle_clear_login()
|
||||
if not self._ensure_playwright(use_storage_state=not force):
|
||||
return {"success": False, "error": self._last_error or "浏览器不可用"}
|
||||
if not self._open_document(doc_url, fast=True):
|
||||
return {"success": False, "error": self._last_error or "打开文档失败"}
|
||||
|
||||
doc_error = self._ensure_doc_access(doc_url, fast=True, use_storage_state=not force)
|
||||
if doc_error:
|
||||
return {"success": False, "error": doc_error}
|
||||
|
||||
if not force and self._has_saved_login_state() and self._is_logged_in():
|
||||
self._login_required = False
|
||||
@@ -850,54 +1024,12 @@ class KDocsUploader:
|
||||
return {"success": True, "logged_in": True, "qr_image": ""}
|
||||
|
||||
fast_login_timeout = int(os.environ.get("KDOCS_FAST_LOGIN_TIMEOUT_MS", "300"))
|
||||
self._ensure_login_dialog(
|
||||
timeout_ms=fast_login_timeout,
|
||||
frame_timeout_ms=fast_login_timeout,
|
||||
quick=True,
|
||||
)
|
||||
qr_image = None
|
||||
invalid_qr = None
|
||||
for attempt in range(10):
|
||||
if attempt in (3, 7):
|
||||
self._ensure_login_dialog(
|
||||
timeout_ms=fast_login_timeout,
|
||||
frame_timeout_ms=fast_login_timeout,
|
||||
quick=True,
|
||||
)
|
||||
candidate = self._capture_qr_image()
|
||||
if candidate and self._is_valid_qr_image(candidate):
|
||||
qr_image = candidate
|
||||
break
|
||||
if candidate:
|
||||
invalid_qr = candidate
|
||||
time.sleep(0.8) # 优化: 1 -> 0.8
|
||||
self._trigger_fast_login_dialog(fast_login_timeout)
|
||||
qr_image, invalid_qr = self._capture_qr_with_retry(fast_login_timeout)
|
||||
|
||||
if not qr_image:
|
||||
self._last_error = "二维码识别异常" if invalid_qr else "二维码获取失败"
|
||||
try:
|
||||
pages = self._iter_pages()
|
||||
page_urls = [getattr(p, "url", "") for p in pages]
|
||||
logger.warning(f"[KDocs] 二维码未捕获,页面: {page_urls}")
|
||||
ts = int(time.time())
|
||||
saved = []
|
||||
for idx, page in enumerate(pages[:3]):
|
||||
try:
|
||||
path = f"data/kdocs_debug_{ts}_{idx}.png"
|
||||
page.screenshot(path=path, full_page=True)
|
||||
saved.append(path)
|
||||
except Exception:
|
||||
continue
|
||||
if saved:
|
||||
logger.warning(f"[KDocs] 已保存调试截图: {saved}")
|
||||
if invalid_qr:
|
||||
try:
|
||||
path = f"data/kdocs_invalid_qr_{ts}.png"
|
||||
with open(path, "wb") as handle:
|
||||
handle.write(invalid_qr)
|
||||
logger.warning(f"[KDocs] 已保存无效二维码截图: {path}")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
self._save_qr_debug_artifacts(invalid_qr)
|
||||
return {"success": False, "error": self._last_error}
|
||||
|
||||
try:
|
||||
@@ -933,24 +1065,22 @@ class KDocsUploader:
|
||||
|
||||
def _handle_status_check(self) -> Dict[str, Any]:
|
||||
cfg = self._load_system_config()
|
||||
doc_url = (cfg.get("kdocs_doc_url") or "").strip()
|
||||
doc_url = self._resolve_doc_url(cfg)
|
||||
if not doc_url:
|
||||
return {"success": True, "logged_in": False, "error": "未配置文档链接"}
|
||||
if not self._ensure_playwright():
|
||||
return {"success": False, "logged_in": False, "error": self._last_error or "浏览器不可用"}
|
||||
if not self._open_document(doc_url, fast=True):
|
||||
return {"success": False, "logged_in": False, "error": self._last_error or "打开文档失败"}
|
||||
|
||||
doc_error = self._ensure_doc_access(doc_url, fast=True)
|
||||
if doc_error:
|
||||
return {"success": False, "logged_in": False, "error": doc_error}
|
||||
|
||||
fast_login_timeout = int(os.environ.get("KDOCS_FAST_LOGIN_TIMEOUT_MS", "300"))
|
||||
self._ensure_login_dialog(
|
||||
timeout_ms=fast_login_timeout,
|
||||
frame_timeout_ms=fast_login_timeout,
|
||||
quick=True,
|
||||
)
|
||||
self._trigger_fast_login_dialog(fast_login_timeout)
|
||||
self._try_confirm_login(
|
||||
timeout_ms=fast_login_timeout,
|
||||
frame_timeout_ms=fast_login_timeout,
|
||||
quick=True,
|
||||
)
|
||||
|
||||
logged_in = self._is_logged_in()
|
||||
self._last_login_ok = logged_in
|
||||
self._login_required = not logged_in
|
||||
@@ -962,79 +1092,43 @@ class KDocsUploader:
|
||||
cfg = self._load_system_config()
|
||||
if int(cfg.get("kdocs_enabled", 0) or 0) != 1:
|
||||
return
|
||||
doc_url = (cfg.get("kdocs_doc_url") or "").strip()
|
||||
|
||||
doc_url = self._resolve_doc_url(cfg)
|
||||
if not doc_url:
|
||||
return
|
||||
|
||||
unit = (payload.get("unit") or "").strip()
|
||||
name = (payload.get("name") or "").strip()
|
||||
image_path = payload.get("image_path")
|
||||
user_id = payload.get("user_id")
|
||||
account_id = payload.get("account_id")
|
||||
|
||||
if not unit or not name:
|
||||
return
|
||||
if not image_path or not os.path.exists(image_path):
|
||||
upload_data = self._parse_upload_payload(payload)
|
||||
if not upload_data:
|
||||
return
|
||||
|
||||
account = None
|
||||
prev_status = None
|
||||
status_tracked = False
|
||||
unit = upload_data["unit"]
|
||||
name = upload_data["name"]
|
||||
image_path = upload_data["image_path"]
|
||||
user_id = upload_data["user_id"]
|
||||
account_id = upload_data["account_id"]
|
||||
|
||||
account, prev_status, status_tracked = self._mark_upload_tracking(user_id, account_id)
|
||||
|
||||
try:
|
||||
try:
|
||||
account = safe_get_account(user_id, account_id)
|
||||
if account and self._should_mark_upload(account):
|
||||
prev_status = getattr(account, "status", None)
|
||||
account.status = "上传截图"
|
||||
self._emit_account_update(user_id, account)
|
||||
status_tracked = True
|
||||
except Exception:
|
||||
prev_status = None
|
||||
|
||||
if not self._ensure_playwright():
|
||||
self._notify_admin(unit, name, image_path, self._last_error or "浏览器不可用")
|
||||
return
|
||||
|
||||
if not self._open_document(doc_url):
|
||||
self._notify_admin(unit, name, image_path, self._last_error or "打开文档失败")
|
||||
doc_error = self._ensure_doc_access(doc_url)
|
||||
if doc_error:
|
||||
self._notify_admin(unit, name, image_path, doc_error)
|
||||
return
|
||||
|
||||
if not self._is_logged_in():
|
||||
self._login_required = True
|
||||
self._last_login_ok = False
|
||||
self._notify_admin(unit, name, image_path, "登录已失效,请管理员重新扫码登录")
|
||||
try:
|
||||
log_to_client("表格上传失败: 登录已失效,请管理员重新扫码登录", user_id, account_id)
|
||||
except Exception:
|
||||
pass
|
||||
self._handle_upload_login_invalid(
|
||||
unit=unit,
|
||||
name=name,
|
||||
image_path=image_path,
|
||||
user_id=user_id,
|
||||
account_id=account_id,
|
||||
)
|
||||
return
|
||||
|
||||
self._login_required = False
|
||||
self._last_login_ok = True
|
||||
|
||||
sheet_name = (cfg.get("kdocs_sheet_name") or "").strip()
|
||||
sheet_index = int(cfg.get("kdocs_sheet_index") or 0)
|
||||
unit_col = (cfg.get("kdocs_unit_column") or "A").strip().upper()
|
||||
image_col = (cfg.get("kdocs_image_column") or "D").strip().upper()
|
||||
row_start = int(cfg.get("kdocs_row_start") or 0)
|
||||
row_end = int(cfg.get("kdocs_row_end") or 0)
|
||||
|
||||
success = False
|
||||
error_msg = ""
|
||||
for attempt in range(2):
|
||||
try:
|
||||
if sheet_name or sheet_index:
|
||||
self._select_sheet(sheet_name, sheet_index)
|
||||
row_num = self._find_person_with_unit(unit, name, unit_col, row_start=row_start, row_end=row_end)
|
||||
if row_num < 0:
|
||||
error_msg = f"未找到人员: {unit}-{name}"
|
||||
break
|
||||
success = self._upload_image_to_cell(row_num, image_path, image_col)
|
||||
if success:
|
||||
break
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
|
||||
success, error_msg = self._try_upload_to_sheet(cfg, unit, name, image_path)
|
||||
if success:
|
||||
self._last_success_at = time.time()
|
||||
self._last_error = None
|
||||
@@ -1048,10 +1142,7 @@ class KDocsUploader:
|
||||
error_msg = "上传失败"
|
||||
self._last_error = error_msg
|
||||
self._notify_admin(unit, name, image_path, error_msg)
|
||||
try:
|
||||
log_to_client(f"表格上传失败: {error_msg}", user_id, account_id)
|
||||
except Exception:
|
||||
pass
|
||||
self._log_upload_failure(error_msg, user_id, account_id)
|
||||
finally:
|
||||
if status_tracked:
|
||||
self._restore_account_status(user_id, account, prev_status)
|
||||
|
||||
Reference in New Issue
Block a user