Files
verify-invoice-default/scripts/service/rpa/login_handler.py
chenfuqiang c5103e2650
All checks were successful
技能自动化发布 / release (push) Successful in 9s
chore: auto release commit (2026-09-14 15:11:41)
2026-09-14 15:11:41 +08:00

208 lines
6.0 KiB
Python

"""登录处理器:处理网站登录流程。"""
from __future__ import annotations
import logging
import time
from typing import Any, Optional
from jiangchang_skill_core.activity import interruptible_sleep
logger = logging.getLogger(__name__)
# 登录相关选择器(需要根据实际页面调整)
LOGIN_SELECTORS = {
"username_input": [
"input[name='username']",
"input[placeholder*='用户名']",
"input[placeholder*='账号']",
"#username",
"[data-testid='username']",
],
"password_input": [
"input[name='password']",
"input[type='password']",
"#password",
"[data-testid='password']",
],
"login_button": [
"button[type='submit']",
"button:has-text('登录')",
"input[type='submit']",
".login-btn",
"[data-testid='login-button']",
],
"captcha_input": [
"input[name='captcha']",
"input[placeholder*='验证码']",
"#captcha",
],
"captcha_image": [
".captcha-image",
"img[alt*='验证码']",
"#captcha-image",
],
}
# 登录成功检测
LOGIN_SUCCESS_SELECTORS = [
".user-info",
".welcome",
"[data-testid='user-avatar']",
"a[href*='logout']",
".dashboard",
]
async def login_if_needed(
page: Any,
username: str,
password: str,
wait_sec: int = 120,
) -> bool:
"""检查登录状态,如需要则执行登录。
Args:
page: Playwright页面对象
username: 用户名
password: 密码
wait_sec: 等待人工验证的超时时间
Returns:
bool: 登录是否成功
"""
# 检查是否已登录
if await _is_logged_in(page):
logger.info("already_logged_in url=%s", page.url)
return True
# 导航到登录页面
if "/login" not in page.url:
await page.goto("https://sandbox.jc2009.com/login", wait_until="domcontentloaded")
await interruptible_sleep(2.0)
# 尝试自动登录
login_success = await _try_auto_login(page, username, password)
if login_success:
return True
# 等待人工登录
return await _wait_for_human_login(page, wait_sec)
async def _try_auto_login(page: Any, username: str, password: str) -> bool:
"""尝试自动登录。"""
try:
# 查找用户名输入框
username_input = await _find_element(page, LOGIN_SELECTORS["username_input"])
if not username_input:
logger.warning("username_input_not_found")
return False
# 查找密码输入框
password_input = await _find_element(page, LOGIN_SELECTORS["password_input"])
if not password_input:
logger.warning("password_input_not_found")
return False
# 输入用户名(模拟人类输入)
await username_input.click()
await interruptible_sleep(0.5)
await username_input.fill("")
await page.keyboard.type(username, delay=100)
await interruptible_sleep(0.5)
# 输入密码
await password_input.click()
await interruptible_sleep(0.5)
await password_input.fill("")
await page.keyboard.type(password, delay=100)
await interruptible_sleep(0.5)
# 查找并点击登录按钮
login_button = await _find_element(page, LOGIN_SELECTORS["login_button"])
if not login_button:
logger.warning("login_button_not_found")
return False
await login_button.click()
await interruptible_sleep(3.0)
# 检查是否登录成功
return await _is_logged_in(page)
except Exception as e:
logger.error("auto_login_failed error=%s", str(e))
return False
async def _wait_for_human_login(page: Any, wait_sec: int) -> bool:
"""等待人工完成登录。"""
print(f"请在浏览器中完成登录,最多等待 {wait_sec} 秒...")
deadline = time.monotonic() + wait_sec
while time.monotonic() < deadline:
if await _is_logged_in(page):
print("检测到登录成功")
return True
# 检查是否有验证码需要处理
captcha_present = await _check_captcha(page)
if captcha_present:
print("检测到验证码,请人工完成验证...")
await interruptible_sleep(2.0)
print("登录超时")
return False
async def _is_logged_in(page: Any) -> bool:
"""检查是否已登录。"""
try:
# 检查URL是否包含登录成功标识
if "/login" not in page.url and "/index" in page.url:
return True
# 检查页面元素
for selector in LOGIN_SUCCESS_SELECTORS:
try:
element = page.locator(selector)
if await element.count() > 0 and await element.is_visible():
return True
except Exception:
continue
return False
except Exception:
return False
async def _check_captcha(page: Any) -> bool:
"""检查是否有验证码。"""
try:
for selector in LOGIN_SELECTORS["captcha_input"]:
element = page.locator(selector)
if await element.count() > 0 and await element.is_visible():
return True
for selector in LOGIN_SELECTORS["captcha_image"]:
element = page.locator(selector)
if await element.count() > 0 and await element.is_visible():
return True
return False
except Exception:
return False
async def _find_element(page: Any, selectors: list) -> Optional[Any]:
"""根据选择器列表查找元素。"""
for selector in selectors:
try:
element = page.locator(selector)
if await element.count() > 0 and await element.is_visible():
return element
except Exception:
continue
return None