1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-18 15:33:03 +08:00
Files
AutoLibrary/src/operators/LibRenew.py
T
KenanZhu 7df6a9157d refactor(LibReserve, LibRenew): 提取时间选择公共逻辑到 LibTimeSelector 基类
将 LibReserve 和 LibRenew 中重复的时间转换和选择逻辑提取到
LibTimeSelector 基类,消除代码重复,提升可维护性。

主要变更:
- 新增 LibTimeSelector 基类,提供时间转换和最佳时间选择算法
- LibReserve 和 LibRenew 继承 LibTimeSelector,移除重复代码
- 拆分过长方法,提升代码可读性
- 修正方法命名 __selectNearstTime -> __selectNearestTime

同时修复续约功能业务逻辑漏洞:
- 新增续约时间上限校验,防止续约时间超过图书馆闭馆时间(23:30)
2026-03-14 14:48:35 +08:00

204 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
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.
See the LICENSE file for details.
"""
import queue
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from base.LibTimeSelector import LibTimeSelector
class LibRenew(LibTimeSelector):
def __init__(
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: WebDriver
):
super().__init__(input_queue, output_queue)
self.__driver = driver
def _waitResponseLoad(
self
) -> bool:
self.__driver.refresh()
return True
def __waitRenewDialog(
self
) -> bool:
try:
WebDriverWait(self.__driver, 2).until(
EC.visibility_of_element_located((By.ID, "extendDiv"))
)
head_message = WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "#extendDiv p.messageHead"))
)
result_message = WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "#extendDiv div.resultMessage"))
)
except:
self._showTrace("续约时间选择界面加载失败 !")
return False
head_message = head_message.text.strip()
if "警告" in head_message:
result_message = result_message.text.strip()
self._showTrace(f"\n"\
f" 续约失败 !\n"\
f" {result_message}")
return False
try:
WebDriverWait(self.__driver, 2).until(
EC.presence_of_all_elements_located(
(By.CSS_SELECTOR, "#extendDiv .renewal_List li")
)
)
WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "#extendDiv .btnOK"))
)
except:
self._showTrace("续约时间选择界面加载失败 !")
return False
return True
def __selectNearestTime(
self,
record: dict,
reserve_info: dict
) -> bool:
"""
Select the nearest available renewal time.
"""
end_time = record["time"]["end"]
renew_info = reserve_info["renew_time"]
max_diff = renew_info["max_diff"]
prefer_earlier = renew_info["prefer_early"]
target_renew_mins = self._timeToMins(end_time) + renew_info["expect_duration"]*60
# Validate and adjust target renew time to library closing time
if not self.__validateAndAdjustRenewTime(end_time, target_renew_mins):
return False
renew_ok_btn = self.__driver.find_element(By.CSS_SELECTOR, "#extendDiv .btnOK")
renew_time_opts = self.__driver.find_elements(By.CSS_SELECTOR, "#extendDiv .renewal_List li")
if not renew_time_opts:
self._showTrace("当前未查询到可用续约时间 !")
return False
# Find best renewal time option
best_opt, best_text, actual_diff, free_times = self._findBestTimeOption(
renew_time_opts, target_renew_mins, max_diff, prefer_earlier, is_reserve=False
)
if best_opt is not None:
return self.__confirmRenewal(best_opt, best_text, actual_diff, record, renew_ok_btn)
self._showTrace(
"无法选择最近的可用续约时间 ! "
f"所有可选时间与目标时间相差都超过了 {max_diff} 分钟 !"
)
self._showTrace(f"当前可供续约的时间有: {free_times}")
return False
def __validateAndAdjustRenewTime(
self,
end_time: str,
target_renew_mins: int
) -> bool:
"""
Validate and adjust renewal time to library closing time if needed.
"""
LIBRARY_CLOSE_TIME = 1410 # 23:30 in minutes
if target_renew_mins > LIBRARY_CLOSE_TIME:
actual_renew_duration = LIBRARY_CLOSE_TIME - self._timeToMins(end_time)
if actual_renew_duration <= 0:
self._showTrace(f"当前结束时间 {end_time} 已接近闭馆时间,无法续约 !")
return False
self._showTrace(
f"续约时间已调整至闭馆时间 {self._minsToTime(LIBRARY_CLOSE_TIME)}"
f"实际续约时长为 {actual_renew_duration//60} 小时 {actual_renew_duration%60} 分钟"
)
return True
return True
def __confirmRenewal(
self,
best_opt,
best_text: str,
actual_diff: int,
record: dict,
ok_btn
) -> bool:
"""
Confirm the selected renewal time.
"""
try:
best_opt.click()
abs_diff = abs(actual_diff)
time_relation = self._formatTimeRelation(abs_diff, actual_diff, "续约时间")
self._showTrace(
f"选择距离期望续约时间最近的 {best_text}, "
f"与期望续约时间相比 {time_relation}"
)
record["time"]["end"] = best_text.strip()
ok_btn.click()
return True
except:
self._showTrace("确认续约时发生错误 !")
return False
def renew(
self,
username: str,
record: dict,
reserve_info: dict
) -> bool:
if self.__driver is None:
self._showTrace("未提供有效 WebDriver 实例 !")
return False
try:
renew_btn = WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.ID, "btnExtend"))
)
except:
self._showTrace(f"用户 {username} 续约界面加载失败 !")
return False
if "disabled" in renew_btn.get_attribute("class"):
self._showTrace(f"用户 {username} 续约按钮不可用, 可能不在场馆内, 请连接图书馆网络后重试")
return False
renew_btn.click()
if not self.__waitRenewDialog():
self._showTrace(f"用户 {username} 续约失败 !")
# After the renewal, the webpage will display a mask overlay,
# so we need to refresh the page for subsequent operations.
self.__driver.refresh()
return False
if not self.__selectNearestTime(record, reserve_info):
self._showTrace(f"用户 {username} 续约失败 !")
self.__driver.refresh()
return False
if self._waitResponseLoad():
return True