mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 14:06:28 +00:00
style(backend): apply ruff format
This commit is contained in:
@@ -42,17 +42,17 @@ def get_live_x_api_payload(auth_token: str) -> str:
|
||||
chrome_options.binary_location = CHROME_BINARY_PATH
|
||||
|
||||
# 开启性能日志记录功能
|
||||
logging_prefs = {'performance': 'ALL'}
|
||||
chrome_options.set_capability('goog:loggingPrefs', logging_prefs)
|
||||
logging_prefs = {"performance": "ALL"}
|
||||
chrome_options.set_capability("goog:loggingPrefs", logging_prefs)
|
||||
|
||||
# Headless 模式配置
|
||||
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36"
|
||||
chrome_options.add_argument(f'user-agent={user_agent}')
|
||||
chrome_options.add_argument(f"user-agent={user_agent}")
|
||||
chrome_options.add_argument("--headless")
|
||||
chrome_options.add_argument("--no-sandbox")
|
||||
chrome_options.add_argument("--disable-dev-shm-usage")
|
||||
chrome_options.add_argument("--window-size=1920,1080")
|
||||
chrome_options.add_argument('--ignore-certificate-errors')
|
||||
chrome_options.add_argument("--ignore-certificate-errors")
|
||||
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
||||
|
||||
driver = webdriver.Chrome(service=service, options=chrome_options)
|
||||
@@ -63,11 +63,7 @@ def get_live_x_api_payload(auth_token: str) -> str:
|
||||
driver.get("https://i.jielong.com/my-class")
|
||||
|
||||
# 注入长期 Token
|
||||
driver.add_cookie({
|
||||
'name': 'token',
|
||||
'value': auth_token,
|
||||
'domain': '.jielong.com'
|
||||
})
|
||||
driver.add_cookie({"name": "token", "value": auth_token, "domain": ".jielong.com"})
|
||||
|
||||
# 导航到触发 API 的页面
|
||||
driver.get("https://i.jielong.com/my-form")
|
||||
@@ -78,14 +74,14 @@ def get_live_x_api_payload(auth_token: str) -> str:
|
||||
found = False
|
||||
|
||||
while time.time() - start_time < max_wait_time:
|
||||
logs = driver.get_log('performance')
|
||||
logs = driver.get_log("performance")
|
||||
for entry in logs:
|
||||
log = json.loads(entry['message'])['message']
|
||||
if log['method'] == 'Network.requestWillBeSent':
|
||||
headers = log.get('params', {}).get('request', {}).get('headers', {})
|
||||
log = json.loads(entry["message"])["message"]
|
||||
if log["method"] == "Network.requestWillBeSent":
|
||||
headers = log.get("params", {}).get("request", {}).get("headers", {})
|
||||
headers_lower = {k.lower(): v for k, v in headers.items()}
|
||||
if 'x-api-request-payload' in headers_lower:
|
||||
payload_signature = headers_lower['x-api-request-payload']
|
||||
if "x-api-request-payload" in headers_lower:
|
||||
payload_signature = headers_lower["x-api-request-payload"]
|
||||
logger.info("成功通过网络日志捕获到现场的 x-api-request-payload!")
|
||||
found = True
|
||||
break
|
||||
@@ -94,12 +90,14 @@ def get_live_x_api_payload(auth_token: str) -> str:
|
||||
time.sleep(1)
|
||||
|
||||
if not payload_signature:
|
||||
raise Exception(f"在 {max_wait_time} 秒内未能通过网络日志捕获到 x-api-request-payload。")
|
||||
raise Exception(
|
||||
f"在 {max_wait_time} 秒内未能通过网络日志捕获到 x-api-request-payload。"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取现场 x-api-request-payload 时失败: {e}")
|
||||
try:
|
||||
debug_screenshot = os.path.join(settings.BASE_DIR, 'payload_debug.png')
|
||||
debug_screenshot = os.path.join(settings.BASE_DIR, "payload_debug.png")
|
||||
driver.save_screenshot(debug_screenshot)
|
||||
except Exception as screenshot_error:
|
||||
logger.warning(f"保存调试截图失败: {screenshot_error}")
|
||||
@@ -135,7 +133,7 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
|
||||
from backend.utils.json_helpers import safe_parse_payload, extract_signature
|
||||
|
||||
payload_dict = safe_parse_payload(task.payload_config)
|
||||
signature = extract_signature(task.payload_config) or 'Unknown'
|
||||
signature = extract_signature(task.payload_config) or "Unknown"
|
||||
|
||||
logger.info(f"Selenium打卡: 正在为任务 ID: {task.id} (Signature: {signature}) 执行打卡...")
|
||||
|
||||
@@ -146,7 +144,7 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
|
||||
"success": False,
|
||||
"status": "failure",
|
||||
"response_text": "",
|
||||
"error_message": error_msg
|
||||
"error_message": error_msg,
|
||||
}
|
||||
|
||||
# 获取 x-api-request-payload
|
||||
@@ -158,7 +156,7 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
|
||||
"success": False,
|
||||
"status": "failure",
|
||||
"response_text": "",
|
||||
"error_message": error_msg
|
||||
"error_message": error_msg,
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -175,19 +173,19 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
|
||||
"success": False,
|
||||
"status": "failure",
|
||||
"response_text": "",
|
||||
"error_message": error_msg
|
||||
"error_message": error_msg,
|
||||
}
|
||||
|
||||
headers = {
|
||||
'User-Agent': "Mozilla%2f5.0+(Linux%3b+Android+16%3b+wv)+AppleWebKit%2f537.36+(KHTML%2c+like+Gecko)+Chrome%2f142.0.0.0+Safari%2f537.36+QQ%2f9.2.30.31620+QQ%2fMiniApp",
|
||||
'Accept-Encoding': "gzip",
|
||||
'Content-Type': "application/json",
|
||||
'authorization': f"Bearer {user_token}",
|
||||
'x-api-request-referer': "https://appservice.qq.com/1110276759",
|
||||
'x-api-request-payload': payload_signature,
|
||||
'referer': "https://appservice.qq.com/1110276759/8.10.1.7/page-frame.html",
|
||||
'platform': "qq",
|
||||
'x-api-request-mode': "cors",
|
||||
"User-Agent": "Mozilla%2f5.0+(Linux%3b+Android+16%3b+wv)+AppleWebKit%2f537.36+(KHTML%2c+like+Gecko)+Chrome%2f142.0.0.0+Safari%2f537.36+QQ%2f9.2.30.31620+QQ%2fMiniApp",
|
||||
"Accept-Encoding": "gzip",
|
||||
"Content-Type": "application/json",
|
||||
"authorization": f"Bearer {user_token}",
|
||||
"x-api-request-referer": "https://appservice.qq.com/1110276759",
|
||||
"x-api-request-payload": payload_signature,
|
||||
"referer": "https://appservice.qq.com/1110276759/8.10.1.7/page-frame.html",
|
||||
"platform": "qq",
|
||||
"x-api-request-mode": "cors",
|
||||
}
|
||||
|
||||
url = "https://api.jielong.com/api/CheckIn/EditRecord"
|
||||
@@ -203,7 +201,9 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
|
||||
response.raise_for_status()
|
||||
response_text = response.text
|
||||
|
||||
logger.info(f"✉️ 任务 ID: {task.id} (Signature: {signature}) 打卡请求完成!响应: {response_text}")
|
||||
logger.info(
|
||||
f"✉️ 任务 ID: {task.id} (Signature: {signature}) 打卡请求完成!响应: {response_text}"
|
||||
)
|
||||
|
||||
# 判断响应内容(参考 V1 实现逻辑)
|
||||
# 情况1: 明确包含"打卡成功" → 成功
|
||||
@@ -213,9 +213,10 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
|
||||
if task.user and task.user.email:
|
||||
try:
|
||||
from backend.services.email_service import EmailService
|
||||
|
||||
task_info = {
|
||||
'thread_id': payload.get('ThreadId', '未知'),
|
||||
'name': getattr(task, 'name', '打卡任务')
|
||||
"thread_id": payload.get("ThreadId", "未知"),
|
||||
"name": getattr(task, "name", "打卡任务"),
|
||||
}
|
||||
EmailService.notify_check_in_result(task.user, task_info, True, "打卡成功")
|
||||
except Exception as e:
|
||||
@@ -225,38 +226,45 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
|
||||
"success": True,
|
||||
"status": "success",
|
||||
"response_text": response_text,
|
||||
"error_message": ""
|
||||
"error_message": "",
|
||||
}
|
||||
|
||||
# 情况2: 已经提交过了(重复提交)→ 视为成功,但不发送邮件
|
||||
# 匹配 "已被提交" 或 "已经打卡"
|
||||
elif ("已被提交" in response_text or "已经打卡" in response_text or
|
||||
"重复提交" in response_text):
|
||||
elif (
|
||||
"已被提交" in response_text
|
||||
or "已经打卡" in response_text
|
||||
or "重复提交" in response_text
|
||||
):
|
||||
logger.info(f"✅ 检测到'已被提交',本次打卡已完成(重复提交,不发送邮件)")
|
||||
return {
|
||||
"success": True,
|
||||
"status": "success",
|
||||
"response_text": response_text,
|
||||
"error_message": ""
|
||||
"error_message": "",
|
||||
}
|
||||
|
||||
# 情况3: 不在打卡时间范围 → 标记为时间范围外
|
||||
# 匹配 Data 或 Description 中的内容
|
||||
elif ("不在打卡时间范围" in response_text or
|
||||
"不在打卡时间" in response_text):
|
||||
elif "不在打卡时间范围" in response_text or "不在打卡时间" in response_text:
|
||||
logger.warning(f"⏰ 检测到'不在打卡时间范围',打卡时间不符")
|
||||
return {
|
||||
"success": False,
|
||||
"status": "out_of_time",
|
||||
"response_text": response_text,
|
||||
"error_message": "不在打卡时间范围内"
|
||||
"error_message": "不在打卡时间范围内",
|
||||
}
|
||||
|
||||
# 情况4: Token 失效的特征标识 → 失败
|
||||
# 扩展检测条件:检测多种 Token 失效的响应特征
|
||||
elif ("登录" in response_text or "授权" in response_text or
|
||||
"未登录" in response_text or "token" in response_text.lower() or
|
||||
"Unauthorized" in response_text or response.status_code == 401):
|
||||
elif (
|
||||
"登录" in response_text
|
||||
or "授权" in response_text
|
||||
or "未登录" in response_text
|
||||
or "token" in response_text.lower()
|
||||
or "Unauthorized" in response_text
|
||||
or response.status_code == 401
|
||||
):
|
||||
logger.warning(f"⚠️ 检测到Token失效特征,Token 可能已失效")
|
||||
# 发送打卡失败邮件通知(邮件内容已包含Token失效提醒和刷新指引)
|
||||
if task.user and task.user.email:
|
||||
@@ -268,7 +276,9 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
|
||||
task_info = build_task_info(task)
|
||||
|
||||
# 只发送打卡失败通知(内容已说明Token失效)
|
||||
EmailService.notify_check_in_result(task.user, task_info, False, "Token 已失效,需要重新授权")
|
||||
EmailService.notify_check_in_result(
|
||||
task.user, task_info, False, "Token 已失效,需要重新授权"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"发送打卡失败邮件失败: {e}")
|
||||
|
||||
@@ -276,7 +286,7 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
|
||||
"success": False,
|
||||
"status": "token_expired", # 特殊状态,用于标识 Token 过期
|
||||
"response_text": response_text,
|
||||
"error_message": "Token 已失效,需要重新授权"
|
||||
"error_message": "Token 已失效,需要重新授权",
|
||||
}
|
||||
|
||||
# 情况5: 其他响应 → 需要人工确认(标记为异常)
|
||||
@@ -287,7 +297,7 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
|
||||
"success": False,
|
||||
"status": "unknown",
|
||||
"response_text": response_text,
|
||||
"error_message": "未识别的响应,请人工确认"
|
||||
"error_message": "未识别的响应,请人工确认",
|
||||
}
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
@@ -303,15 +313,10 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
|
||||
"success": False,
|
||||
"status": "failure",
|
||||
"response_text": response_text,
|
||||
"error_message": str(e)
|
||||
"error_message": str(e),
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"为任务 ID: {task.id} (Signature: {signature}) 打卡时发生未知错误: {e}"
|
||||
logger.error(error_msg)
|
||||
return {
|
||||
"success": False,
|
||||
"status": "failure",
|
||||
"response_text": "",
|
||||
"error_message": str(e)
|
||||
}
|
||||
return {"success": False, "status": "failure", "response_text": "", "error_message": str(e)}
|
||||
|
||||
@@ -32,7 +32,9 @@ class EmailNotifier:
|
||||
"""
|
||||
# 检查必要的邮件配置是否存在
|
||||
if not settings.SMTP_SERVER or not settings.SMTP_SENDER_EMAIL:
|
||||
logger.debug("邮件配置未完成(SMTP_SERVER 或 SMTP_SENDER_EMAIL 为空),邮件发送功能已禁用")
|
||||
logger.debug(
|
||||
"邮件配置未完成(SMTP_SERVER 或 SMTP_SENDER_EMAIL 为空),邮件发送功能已禁用"
|
||||
)
|
||||
return None
|
||||
|
||||
if not settings.SMTP_PORT:
|
||||
@@ -41,19 +43,16 @@ class EmailNotifier:
|
||||
|
||||
# 返回配置字典
|
||||
return {
|
||||
'smtp_server': settings.SMTP_SERVER,
|
||||
'smtp_port': settings.SMTP_PORT,
|
||||
'sender_email': settings.SMTP_SENDER_EMAIL,
|
||||
'sender_password': settings.SMTP_SENDER_PASSWORD,
|
||||
'use_ssl': settings.SMTP_USE_SSL
|
||||
"smtp_server": settings.SMTP_SERVER,
|
||||
"smtp_port": settings.SMTP_PORT,
|
||||
"sender_email": settings.SMTP_SENDER_EMAIL,
|
||||
"sender_password": settings.SMTP_SENDER_PASSWORD,
|
||||
"use_ssl": settings.SMTP_USE_SSL,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def send_email(
|
||||
to_emails: List[str],
|
||||
subject: str,
|
||||
html_content: str,
|
||||
from_email: Optional[str] = None
|
||||
to_emails: List[str], subject: str, html_content: str, from_email: Optional[str] = None
|
||||
) -> bool:
|
||||
"""
|
||||
发送邮件(底层方法)
|
||||
@@ -74,30 +73,26 @@ class EmailNotifier:
|
||||
|
||||
try:
|
||||
# 创建邮件
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg['From'] = from_email or email_config['sender_email']
|
||||
msg['To'] = ', '.join(to_emails)
|
||||
msg['Subject'] = subject
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = from_email or email_config["sender_email"]
|
||||
msg["To"] = ", ".join(to_emails)
|
||||
msg["Subject"] = subject
|
||||
|
||||
# 添加 HTML 正文
|
||||
html_part = MIMEText(html_content, 'html', 'utf-8')
|
||||
html_part = MIMEText(html_content, "html", "utf-8")
|
||||
msg.attach(html_part)
|
||||
|
||||
# 连接 SMTP 服务器并发送
|
||||
if email_config.get('use_ssl', True):
|
||||
if email_config.get("use_ssl", True):
|
||||
server = smtplib.SMTP_SSL(
|
||||
email_config['smtp_server'],
|
||||
int(email_config['smtp_port'])
|
||||
email_config["smtp_server"], int(email_config["smtp_port"])
|
||||
)
|
||||
else:
|
||||
server = smtplib.SMTP(
|
||||
email_config['smtp_server'],
|
||||
int(email_config['smtp_port'])
|
||||
)
|
||||
server = smtplib.SMTP(email_config["smtp_server"], int(email_config["smtp_port"]))
|
||||
server.starttls()
|
||||
|
||||
server.login(email_config['sender_email'], email_config['sender_password'])
|
||||
server.sendmail(msg['From'], to_emails, msg.as_string())
|
||||
server.login(email_config["sender_email"], email_config["sender_password"])
|
||||
server.sendmail(msg["From"], to_emails, msg.as_string())
|
||||
server.quit()
|
||||
|
||||
logger.info(f"邮件发送成功: {subject} -> {', '.join(to_emails)}")
|
||||
@@ -116,4 +111,3 @@ class EmailNotifier:
|
||||
邮件功能是否可用
|
||||
"""
|
||||
return EmailNotifier.get_email_config() is not None
|
||||
|
||||
|
||||
@@ -27,11 +27,10 @@ def get_chrome_config():
|
||||
"""获取 Chrome 配置(从 settings 读取)"""
|
||||
return {
|
||||
"chrome_binary": settings.CHROME_BINARY_PATH,
|
||||
"chromedriver": settings.CHROMEDRIVER_PATH
|
||||
"chromedriver": settings.CHROMEDRIVER_PATH,
|
||||
}
|
||||
|
||||
|
||||
|
||||
def update_session_file(session_id: str, data: dict) -> None:
|
||||
"""线程安全地写入会话文件"""
|
||||
filepath = settings.SESSION_DIR / f"{session_id}.json"
|
||||
@@ -39,7 +38,7 @@ def update_session_file(session_id: str, data: dict) -> None:
|
||||
|
||||
try:
|
||||
with FileLock(lock_path, timeout=5):
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
with open(filepath, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
except Exception as e:
|
||||
logger.error(f"写入会话文件 {filepath} 失败: {e}")
|
||||
@@ -55,13 +54,14 @@ def get_session_status(session_id: str) -> str:
|
||||
|
||||
try:
|
||||
with FileLock(lock_path, timeout=5):
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
if not content:
|
||||
return None
|
||||
from backend.utils.json_helpers import safe_parse_json
|
||||
|
||||
data = safe_parse_json(content, {})
|
||||
return data.get('status')
|
||||
return data.get("status")
|
||||
except IOError as e:
|
||||
logger.error(f"读取会话文件 {filepath} 失败: {e}")
|
||||
return None
|
||||
@@ -77,11 +77,12 @@ def get_session_data(session_id: str) -> dict:
|
||||
|
||||
try:
|
||||
with FileLock(lock_path, timeout=5):
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
if not content:
|
||||
return None
|
||||
from backend.utils.json_helpers import safe_parse_json
|
||||
|
||||
return safe_parse_json(content, {})
|
||||
except IOError as e:
|
||||
logger.error(f"读取会话文件 {filepath} 失败: {e}")
|
||||
@@ -110,23 +111,23 @@ def cancel_session(session_id: str) -> bool:
|
||||
# 读取当前会话数据
|
||||
from backend.utils.json_helpers import safe_parse_json
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
if not content:
|
||||
return False
|
||||
data = safe_parse_json(content, {})
|
||||
|
||||
# 如果已经成功,不允许取消
|
||||
if data.get('status') == 'success':
|
||||
if data.get("status") == "success":
|
||||
logger.info(f"会话 {session_id} 已成功,无法取消")
|
||||
return False
|
||||
|
||||
# 标记为已取消
|
||||
data['status'] = 'cancelled'
|
||||
data['message'] = '用户取消登录'
|
||||
data["status"] = "cancelled"
|
||||
data["message"] = "用户取消登录"
|
||||
|
||||
# 写回文件
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
with open(filepath, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"✅ 会话 {session_id} 已取消")
|
||||
@@ -137,7 +138,9 @@ def cancel_session(session_id: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def get_token_headless(session_id: str, jwt_sub: str = None, alias: str = None, client_ip: str = "") -> None:
|
||||
def get_token_headless(
|
||||
session_id: str, jwt_sub: str = None, alias: str = None, client_ip: str = ""
|
||||
) -> None:
|
||||
"""
|
||||
使用 Selenium 获取 QQ 扫码登录的 Token
|
||||
|
||||
@@ -171,12 +174,12 @@ def get_token_headless(session_id: str, jwt_sub: str = None, alias: str = None,
|
||||
|
||||
# Headless 模式配置
|
||||
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36"
|
||||
chrome_options.add_argument(f'user-agent={user_agent}')
|
||||
chrome_options.add_argument(f"user-agent={user_agent}")
|
||||
chrome_options.add_argument("--headless")
|
||||
chrome_options.add_argument("--no-sandbox")
|
||||
chrome_options.add_argument("--disable-dev-shm-usage")
|
||||
chrome_options.add_argument("--window-size=1920,1080")
|
||||
chrome_options.add_argument('--ignore-certificate-errors')
|
||||
chrome_options.add_argument("--ignore-certificate-errors")
|
||||
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
||||
|
||||
# 启动浏览器
|
||||
@@ -203,7 +206,9 @@ def get_token_headless(session_id: str, jwt_sub: str = None, alias: str = None,
|
||||
current_step = "查找并点击切换按钮"
|
||||
toggle_button_selector = "div.login-wrap .toggle"
|
||||
logger.info(f"Selenium ({session_id}): {current_step} ({toggle_button_selector})...")
|
||||
toggle_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, toggle_button_selector)))
|
||||
toggle_button = wait.until(
|
||||
EC.element_to_be_clickable((By.CSS_SELECTOR, toggle_button_selector))
|
||||
)
|
||||
toggle_button.click()
|
||||
|
||||
# --- 步骤 2: 勾选同意服务协议 ---
|
||||
@@ -219,27 +224,35 @@ def get_token_headless(session_id: str, jwt_sub: str = None, alias: str = None,
|
||||
current_step = "点击立即登录按钮"
|
||||
login_button_selector = "button.css-1wli0ry.ant-btn.ant-btn-default.login-btn"
|
||||
logger.info(f"Selenium ({session_id}): {current_step} ({login_button_selector})...")
|
||||
login_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, login_button_selector)))
|
||||
login_button = wait.until(
|
||||
EC.element_to_be_clickable((By.CSS_SELECTOR, login_button_selector))
|
||||
)
|
||||
login_button.click()
|
||||
|
||||
# --- 步骤 4: 等待二维码加载 ---
|
||||
import time
|
||||
|
||||
time.sleep(3) # 等待几秒让二维码刷新出来
|
||||
|
||||
current_step = "等待QQ二维码图片加载"
|
||||
qq_qr_image_selector = "#login_container img"
|
||||
logger.info(f"Selenium ({session_id}): {current_step} ({qq_qr_image_selector})...")
|
||||
qr_element = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, qq_qr_image_selector)))
|
||||
qr_element = wait.until(
|
||||
EC.visibility_of_element_located((By.CSS_SELECTOR, qq_qr_image_selector))
|
||||
)
|
||||
|
||||
logger.info(f"Selenium ({session_id}): 成功找到QQ二维码元素,正在截图...")
|
||||
qr_base64 = qr_element.screenshot_as_base64
|
||||
update_session_file(session_id, {
|
||||
'status': 'waiting_scan',
|
||||
'qr_image_data': qr_base64,
|
||||
'jwt_sub': jwt_sub,
|
||||
'alias': alias, # 新增:保存 alias
|
||||
'client_ip': client_ip # 新增:保存 IP
|
||||
})
|
||||
update_session_file(
|
||||
session_id,
|
||||
{
|
||||
"status": "waiting_scan",
|
||||
"qr_image_data": qr_base64,
|
||||
"jwt_sub": jwt_sub,
|
||||
"alias": alias, # 新增:保存 alias
|
||||
"client_ip": client_ip, # 新增:保存 IP
|
||||
},
|
||||
)
|
||||
|
||||
current_step = "等待用户扫描登录 (Cookie 'token' 出现)"
|
||||
cookie_name_to_find = "token"
|
||||
@@ -248,10 +261,11 @@ def get_token_headless(session_id: str, jwt_sub: str = None, alias: str = None,
|
||||
# 自定义等待逻辑:每秒检查cookie和session状态
|
||||
max_wait_seconds = 120
|
||||
import time
|
||||
|
||||
for i in range(max_wait_seconds):
|
||||
# 检查session是否被取消
|
||||
status = get_session_status(session_id)
|
||||
if status == 'cancelled':
|
||||
if status == "cancelled":
|
||||
logger.info(f"Selenium ({session_id}): 用户取消了登录,终止会话")
|
||||
raise Exception("用户取消登录")
|
||||
|
||||
@@ -268,22 +282,28 @@ def get_token_headless(session_id: str, jwt_sub: str = None, alias: str = None,
|
||||
cookie = driver.get_cookie(cookie_name_to_find)
|
||||
if cookie:
|
||||
logger.info(f"Selenium ({session_id}): 成功在Cookie中捕获到Token!")
|
||||
update_session_file(session_id, {
|
||||
'status': 'success',
|
||||
'token': cookie['value'],
|
||||
'alias': alias, # 保存 alias
|
||||
'client_ip': client_ip # 保存 IP
|
||||
})
|
||||
update_session_file(
|
||||
session_id,
|
||||
{
|
||||
"status": "success",
|
||||
"token": cookie["value"],
|
||||
"alias": alias, # 保存 alias
|
||||
"client_ip": client_ip, # 保存 IP
|
||||
},
|
||||
)
|
||||
else:
|
||||
raise Exception("等待Cookie成功但获取失败")
|
||||
|
||||
except TimeoutException:
|
||||
if get_session_status(session_id) == 'success':
|
||||
logger.warning(f"Selenium ({session_id}): 一个并发线程超时,但会话已成功,将忽略此超时。")
|
||||
if get_session_status(session_id) == "success":
|
||||
logger.warning(
|
||||
f"Selenium ({session_id}): 一个并发线程超时,但会话已成功,将忽略此超时。"
|
||||
)
|
||||
else:
|
||||
# 释放预占的用户名
|
||||
if alias:
|
||||
from backend.services.registration_manager import registration_manager
|
||||
|
||||
registration_manager.release_alias(alias, session_id)
|
||||
logger.info(f"超时释放用户名预占: {alias}")
|
||||
|
||||
@@ -294,34 +314,35 @@ def get_token_headless(session_id: str, jwt_sub: str = None, alias: str = None,
|
||||
if driver:
|
||||
try:
|
||||
driver.save_screenshot(DEBUG_SCREENSHOT_PATH)
|
||||
with open(DEBUG_PAGE_SOURCE_PATH, 'w', encoding='utf-8') as f:
|
||||
with open(DEBUG_PAGE_SOURCE_PATH, "w", encoding="utf-8") as f:
|
||||
f.write(driver.page_source)
|
||||
logger.error(f"Selenium ({session_id}): 调试截图和源码已保存。当前URL: {driver.current_url}")
|
||||
logger.error(
|
||||
f"Selenium ({session_id}): 调试截图和源码已保存。当前URL: {driver.current_url}"
|
||||
)
|
||||
except Exception as debug_error:
|
||||
logger.error(f"Selenium ({session_id}): 保存调试信息失败: {debug_error}")
|
||||
|
||||
update_session_file(session_id, {
|
||||
'status': 'error',
|
||||
'message': error_message,
|
||||
'jwt_sub': jwt_sub
|
||||
})
|
||||
update_session_file(
|
||||
session_id, {"status": "error", "message": error_message, "jwt_sub": jwt_sub}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
if get_session_status(session_id) == 'success':
|
||||
logger.warning(f"Selenium ({session_id}): 一个并发线程出错 ({e}),但会话已成功,将忽略此错误。")
|
||||
if get_session_status(session_id) == "success":
|
||||
logger.warning(
|
||||
f"Selenium ({session_id}): 一个并发线程出错 ({e}),但会话已成功,将忽略此错误。"
|
||||
)
|
||||
else:
|
||||
# 释放预占的用户名
|
||||
if alias:
|
||||
from backend.services.registration_manager import registration_manager
|
||||
|
||||
registration_manager.release_alias(alias, session_id)
|
||||
logger.info(f"异常释放用户名预占: {alias}")
|
||||
|
||||
logger.error(f"Selenium ({session_id}): 发生未知错误: {e}", exc_info=True)
|
||||
update_session_file(session_id, {
|
||||
'status': 'error',
|
||||
'message': str(e),
|
||||
'jwt_sub': jwt_sub
|
||||
})
|
||||
update_session_file(
|
||||
session_id, {"status": "error", "message": str(e), "jwt_sub": jwt_sub}
|
||||
)
|
||||
|
||||
finally:
|
||||
if driver:
|
||||
|
||||
Reference in New Issue
Block a user