1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-17 23:13:03 +08:00

refactor(pages): 统一命名规范并修复 SeatMapOverlay 元素等待目标错误

- AutoLibPages → AutoLib(移除实现细节后缀)
- ReserveValidator → ReserveChecker(与 RecordChecker 命名一致)
- CaptchaHandler → CaptchaSolver(语义更准确,职责是"求解"验证码)
- ReserveChecker.validate() → check()(与 RecordChecker 风格统一)
- 修复 SeatMapOverlay.selectSeat() 中 _waitClickable 等待页面全局
  <a> 而非具体 seat_link 元素的时序缺陷
- ALMainWorkers 切换为 pages.AutoLib 新版实现

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 20:52:52 +08:00
parent 280028259f
commit caa563e770
10 changed files with 27 additions and 37 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ from PySide6.QtCore import (
) )
from base.MsgBase import MsgBase from base.MsgBase import MsgBase
from operators.AutoLib import AutoLib from pages.AutoLib import AutoLib
from utils.JSONReader import JSONReader from utils.JSONReader import JSONReader
from autoscript import createEngine from autoscript import createEngine
@@ -24,12 +24,12 @@ from pages.MainShell import MainShell
from pages.flows.ReserveFlow import ReserveFlow, ReserveContext from pages.flows.ReserveFlow import ReserveFlow, ReserveContext
from pages.flows.CheckinFlow import CheckinFlow from pages.flows.CheckinFlow import CheckinFlow
from pages.flows.RenewFlow import RenewFlow from pages.flows.RenewFlow import RenewFlow
from pages.services.CaptchaHandler import CaptchaHandler from pages.services.CaptchaSolver import CaptchaSolver
from pages.services.ReserveValidator import ReserveValidator from pages.services.ReserveChecker import ReserveChecker
from pages.services.RecordChecker import RecordChecker from pages.services.RecordChecker import RecordChecker
class AutoLibPages(MsgBase): class AutoLib(MsgBase):
def __init__( def __init__(
self, self,
@@ -46,9 +46,9 @@ class AutoLibPages(MsgBase):
self.__driver_path: str = "" self.__driver_path: str = ""
self.__login_page: LoginPage = None self.__login_page: LoginPage = None
self.__shell: MainShell = None self.__shell: MainShell = None
self.__captcha_handler: CaptchaHandler = None self.__captcha_solver: CaptchaSolver = None
self.__record_checker: RecordChecker = None self.__record_checker: RecordChecker = None
self.__reserve_validator: ReserveValidator = None self.__reserve_checker: ReserveChecker = None
self.__reserve_flow: ReserveFlow = None self.__reserve_flow: ReserveFlow = None
self.__checkin_flow: CheckinFlow = None self.__checkin_flow: CheckinFlow = None
self.__renew_flow: RenewFlow = None self.__renew_flow: RenewFlow = None
@@ -189,7 +189,7 @@ class AutoLibPages(MsgBase):
self._showTrace("浏览器驱动未初始化, 请先初始化浏览器驱动 !", self.TraceLevel.WARNING) self._showTrace("浏览器驱动未初始化, 请先初始化浏览器驱动 !", self.TraceLevel.WARNING)
return return
self.__shell = MainShell(self.__driver) self.__shell = MainShell(self.__driver)
self.__captcha_handler = CaptchaHandler( self.__captcha_solver = CaptchaSolver(
input_queue=self._input_queue, input_queue=self._input_queue,
output_queue=self._output_queue, output_queue=self._output_queue,
) )
@@ -197,7 +197,7 @@ class AutoLibPages(MsgBase):
input_queue=self._input_queue, input_queue=self._input_queue,
output_queue=self._output_queue, output_queue=self._output_queue,
) )
self.__reserve_validator = ReserveValidator( self.__reserve_checker = ReserveChecker(
input_queue=self._input_queue, input_queue=self._input_queue,
output_queue=self._output_queue, output_queue=self._output_queue,
) )
@@ -242,7 +242,7 @@ class AutoLibPages(MsgBase):
if not self.__login_page.login( if not self.__login_page.login(
username, username,
password, password,
captcha_solver=self.__captcha_handler.solveCaptcha, captcha_solver=self.__captcha_solver.solveCaptcha,
auto_captcha=auto_captcha, auto_captcha=auto_captcha,
max_attempts=login_config.get("max_attempt", 3), max_attempts=login_config.get("max_attempt", 3),
): ):
@@ -256,7 +256,7 @@ class AutoLibPages(MsgBase):
# reserve # reserve
if run_mode["auto_reserve"]: if run_mode["auto_reserve"]:
if self.__record_checker.canReserve(self.__shell, reserve_info.get("date")): if self.__record_checker.canReserve(self.__shell, reserve_info.get("date")):
if self.__reserve_validator.validate(reserve_info): if self.__reserve_checker.check(reserve_info):
ctx = ReserveContext( ctx = ReserveContext(
username=username, username=username,
date=reserve_info["date"], date=reserve_info["date"],
+2 -2
View File
@@ -7,7 +7,7 @@ This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License. You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details. See the LICENSE file for details.
""" """
from pages.AutoLibPages import AutoLibPages from pages.AutoLib import AutoLib
from pages.LoginPage import LoginPage from pages.LoginPage import LoginPage
from pages.MainShell import MainShell from pages.MainShell import MainShell
from pages.ReserveView import ReserveView from pages.ReserveView import ReserveView
@@ -19,7 +19,7 @@ from pages.components.CheckinResultDialog import CheckinResultDialog
from pages.components.RenewDialog import RenewDialog from pages.components.RenewDialog import RenewDialog
__all__ = [ __all__ = [
"AutoLibPages", "AutoLib",
"LoginPage", "LoginPage",
"MainShell", "MainShell",
"ReserveView", "ReserveView",
+8 -2
View File
@@ -15,6 +15,8 @@ from selenium.common.exceptions import (
) )
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pages.components.Overlay import Overlay from pages.components.Overlay import Overlay
@@ -48,7 +50,9 @@ class SeatMapOverlay(Overlay):
try: try:
seat_el = self._find(By.ID, f"seat_{int(seat_id):03d}") seat_el = self._find(By.ID, f"seat_{int(seat_id):03d}")
seat_link = seat_el.find_element(By.TAG_NAME, "a") seat_link = seat_el.find_element(By.TAG_NAME, "a")
self._waitClickable((By.TAG_NAME, "a")) WebDriverWait(self._driver, 2).until(
EC.element_to_be_clickable(seat_link)
)
seat_link.click() seat_link.click()
return seat_link.get_attribute("title") return seat_link.get_attribute("title")
except (NoSuchElementException, ValueError, TimeoutException, except (NoSuchElementException, ValueError, TimeoutException,
@@ -63,7 +67,9 @@ class SeatMapOverlay(Overlay):
if not seat_id_upper == seat.text.lstrip('0'): if not seat_id_upper == seat.text.lstrip('0'):
continue continue
seat_link = seat.find_element(By.TAG_NAME, "a") seat_link = seat.find_element(By.TAG_NAME, "a")
self._waitClickable((By.TAG_NAME, "a")) WebDriverWait(self._driver, 2).until(
EC.element_to_be_clickable(seat_link)
)
seat_link.click() seat_link.click()
return seat_link.get_attribute("title") return seat_link.get_attribute("title")
return None return None
-3
View File
@@ -42,16 +42,13 @@ class CheckinFlow(MsgBase):
if not self._shell.waitCheckinButton(): if not self._shell.waitCheckinButton():
self._showTrace(f"用户 {username} 签到界面加载失败 !", self.TraceLevel.ERROR) self._showTrace(f"用户 {username} 签到界面加载失败 !", self.TraceLevel.ERROR)
return False return False
if self._shell.isCheckinButtonDisabled(): if self._shell.isCheckinButtonDisabled():
self._showTrace("签到按钮不可用, 可能不在场馆内, 正在尝试启用......") self._showTrace("签到按钮不可用, 可能不在场馆内, 正在尝试启用......")
if not self._shell.enableCheckinButtonByJS(): if not self._shell.enableCheckinButtonByJS():
self._showTrace(f"签到按钮启用失败 !", self.TraceLevel.ERROR) self._showTrace(f"签到按钮启用失败 !", self.TraceLevel.ERROR)
return False return False
self._showTrace("签到按钮已启用") self._showTrace("签到按钮已启用")
self._shell.clickCheckinButton() self._shell.clickCheckinButton()
try: try:
with CheckinResultDialog(self._driver) as dialog: with CheckinResultDialog(self._driver) as dialog:
result_msg = dialog.getResultMessage() result_msg = dialog.getResultMessage()
-8
View File
@@ -53,23 +53,18 @@ class RenewFlow(MsgBase):
prefer_earlier = renew_info["prefer_early"] prefer_earlier = renew_info["prefer_early"]
end_time = record["time"]["end"] end_time = record["time"]["end"]
target_renew_mins = timeStrToMins(end_time) + renew_info["expect_duration"] * 60 target_renew_mins = timeStrToMins(end_time) + renew_info["expect_duration"] * 60
if not self._validateRenewTime(end_time, target_renew_mins): if not self._validateRenewTime(end_time, target_renew_mins):
return False return False
if not self._shell.waitExtendButton(): if not self._shell.waitExtendButton():
self._showTrace(f"用户 {username} 续约界面加载失败 !", self.TraceLevel.ERROR) self._showTrace(f"用户 {username} 续约界面加载失败 !", self.TraceLevel.ERROR)
return False return False
if self._shell.isExtendButtonDisabled(): if self._shell.isExtendButtonDisabled():
self._showTrace( self._showTrace(
f"用户 {username} 续约按钮不可用, 可能不在场馆内, " f"用户 {username} 续约按钮不可用, 可能不在场馆内, "
f"请连接图书馆网络后重试" f"请连接图书馆网络后重试"
) )
return False return False
self._shell.clickExtendButton() self._shell.clickExtendButton()
try: try:
with RenewDialog(self._driver) as dialog: with RenewDialog(self._driver) as dialog:
if not dialog.waitUntilReady(): if not dialog.waitUntilReady():
@@ -82,14 +77,12 @@ class RenewFlow(MsgBase):
self._shell.refresh() self._shell.refresh()
self._showTrace(f"用户 {username} 续约失败 !", self.TraceLevel.ERROR) self._showTrace(f"用户 {username} 续约失败 !", self.TraceLevel.ERROR)
return False return False
renew_ok_btn = dialog.getOkButton() renew_ok_btn = dialog.getOkButton()
renew_time_opts = dialog.getTimeOptions() renew_time_opts = dialog.getTimeOptions()
if not renew_time_opts: if not renew_time_opts:
self._showTrace("当前未查询到可用续约时间 !", self.TraceLevel.WARNING) self._showTrace("当前未查询到可用续约时间 !", self.TraceLevel.WARNING)
self._shell.refresh() self._shell.refresh()
return False return False
best_opt, best_text, actual_diff, free_times = findBestTimeOption( best_opt, best_text, actual_diff, free_times = findBestTimeOption(
renew_time_opts, target_renew_mins, max_diff, prefer_earlier, renew_time_opts, target_renew_mins, max_diff, prefer_earlier,
is_reserve=False, is_reserve=False,
@@ -111,7 +104,6 @@ class RenewFlow(MsgBase):
renew_ok_btn.click() renew_ok_btn.click()
self._shell.refresh() self._shell.refresh()
return True return True
self._showTrace( self._showTrace(
"无法选择最近的可用续约时间 ! " "无法选择最近的可用续约时间 ! "
f"所有可选时间与目标时间相差都超过了 {max_diff} 分钟 !", f"所有可选时间与目标时间相差都超过了 {max_diff} 分钟 !",
-2
View File
@@ -68,7 +68,6 @@ def findBestTimeOption(
) )
actual_diff = time_val - target_time actual_diff = time_val - target_time
abs_diff = abs(actual_diff) abs_diff = abs(actual_diff)
if abs_diff < best_time_diff or ( if abs_diff < best_time_diff or (
abs_diff == best_time_diff abs_diff == best_time_diff
and ( and (
@@ -79,7 +78,6 @@ def findBestTimeOption(
best_time_diff = abs_diff best_time_diff = abs_diff
best_actual_diff = actual_diff best_actual_diff = actual_diff
best_time_opt = time_opt best_time_opt = time_opt
if best_time_opt is not None: if best_time_opt is not None:
return (best_time_opt, best_time_opt.text.strip(), best_actual_diff, free_times) return (best_time_opt, best_time_opt.text.strip(), best_actual_diff, free_times)
return (None, None, None, free_times) return (None, None, None, free_times)
@@ -20,7 +20,7 @@ from base.MsgBase import MsgBase
from pages.LoginPage import LoginPage from pages.LoginPage import LoginPage
class CaptchaHandler(MsgBase): class CaptchaSolver(MsgBase):
def __init__( def __init__(
self, self,
@@ -15,7 +15,7 @@ from pages.ReserveView import ReserveView
from pages.flows._helpers import timeStrToMins, minsToTimeStr from pages.flows._helpers import timeStrToMins, minsToTimeStr
class ReserveValidator(MsgBase): class ReserveChecker(MsgBase):
def __init__( def __init__(
self, self,
@@ -150,7 +150,6 @@ class ReserveValidator(MsgBase):
end_time = reserve_info["end_time"] end_time = reserve_info["end_time"]
begin_mins = timeStrToMins(begin_time["time"]) begin_mins = timeStrToMins(begin_time["time"])
end_mins = timeStrToMins(end_time["time"]) end_mins = timeStrToMins(end_time["time"])
if end_mins < begin_mins and reserve_info["satisfy_duration"] is False: if end_mins < begin_mins and reserve_info["satisfy_duration"] is False:
self._showTrace( self._showTrace(
f"结束时间 {end_time['time']} 早于开始时间 {begin_time['time']}, " f"结束时间 {end_time['time']} 早于开始时间 {begin_time['time']}, "
@@ -161,7 +160,6 @@ class ReserveValidator(MsgBase):
begin_time, end_time = end_time, begin_time begin_time, end_time = end_time, begin_time
begin_mins = timeStrToMins(begin_time["time"]) begin_mins = timeStrToMins(begin_time["time"])
end_mins = timeStrToMins(end_time["time"]) end_mins = timeStrToMins(end_time["time"])
max_end_mins = timeStrToMins("23:30") max_end_mins = timeStrToMins("23:30")
if end_mins > max_end_mins: if end_mins > max_end_mins:
self._showTrace( self._showTrace(
@@ -170,7 +168,6 @@ class ReserveValidator(MsgBase):
) )
reserve_info["end_time"]["time"] = "23:30" reserve_info["end_time"]["time"] = "23:30"
end_mins = max_end_mins end_mins = max_end_mins
if reserve_info["satisfy_duration"]: if reserve_info["satisfy_duration"]:
if reserve_info["expect_duration"] > 8: if reserve_info["expect_duration"] > 8:
self._showTrace( self._showTrace(
@@ -191,7 +188,7 @@ class ReserveValidator(MsgBase):
reserve_info["end_time"]["time"] = minsToTimeStr(begin_mins + 8 * 60) reserve_info["end_time"]["time"] = minsToTimeStr(begin_mins + 8 * 60)
return True return True
def validate( def check(
self, self,
reserve_info: dict, reserve_info: dict,
) -> bool: ) -> bool:
+4 -4
View File
@@ -7,12 +7,12 @@ This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License. You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details. See the LICENSE file for details.
""" """
from pages.services.CaptchaHandler import CaptchaHandler from pages.services.CaptchaSolver import CaptchaSolver
from pages.services.ReserveValidator import ReserveValidator from pages.services.ReserveChecker import ReserveChecker
from pages.services.RecordChecker import RecordChecker from pages.services.RecordChecker import RecordChecker
__all__ = [ __all__ = [
"CaptchaHandler", "CaptchaSolver",
"ReserveValidator", "ReserveChecker",
"RecordChecker", "RecordChecker",
] ]