init
This commit is contained in:
210
step1.py
Normal file
210
step1.py
Normal file
@@ -0,0 +1,210 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from typing import Optional, Callable
|
||||
|
||||
from config_loader import get_config
|
||||
|
||||
|
||||
def _now_datestr() -> str:
|
||||
from datetime import datetime
|
||||
return datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
def _safe_screenshot(page, save_path: str) -> None:
|
||||
try:
|
||||
page.screenshot(path=save_path, full_page=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class Step1FeastCoding:
|
||||
"""
|
||||
Step 1: 访问 feast.yidaima.cn,登录并点击 FeastCoding 按钮
|
||||
参考: docs/step1.md
|
||||
"""
|
||||
|
||||
def __init__(self, chrome_path: str, username: str, password: str, log_callback: Optional[Callable[[str], None]] = None):
|
||||
self.chrome_path = chrome_path
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.url = "https://feast.yidaima.cn/login"
|
||||
# self.url = "http://localhost/login"
|
||||
self.project_name = "【A173】基于Springboot + vue3实现的学生交流互助平台"
|
||||
self.feast_button = "FeastCoding"
|
||||
self.browser = None
|
||||
self.page = None
|
||||
self.log_callback = log_callback
|
||||
|
||||
def _log(self, message: str):
|
||||
"""输出日志,支持回调到 GUI"""
|
||||
print(message)
|
||||
if self.log_callback:
|
||||
self.log_callback(message)
|
||||
|
||||
def run(self) -> bool:
|
||||
"""执行 Step 1 流程,成功返回 True"""
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
save_dir = os.path.join(".", "data", "screenshots", _now_datestr())
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
err_path = os.path.join(save_dir, "step1_error.png")
|
||||
|
||||
try:
|
||||
with sync_playwright() as p:
|
||||
self.browser = p.chromium.launch(headless=False, executable_path=self.chrome_path)
|
||||
context = self.browser.new_context(viewport={"width": 1280, "height": 800})
|
||||
self.page = context.new_page()
|
||||
|
||||
# 1) 登录页
|
||||
self._log("[Step1] 正在打开登录页...")
|
||||
self.page.goto(self.url, wait_until="networkidle")
|
||||
self.page.wait_for_timeout(2000)
|
||||
|
||||
# 等待输入框出现
|
||||
self.page.wait_for_selector("input[type='text'], input[name*='user' i], input[placeholder*='用' i]", timeout=15000)
|
||||
|
||||
# 查找用户名和密码输入框
|
||||
user_candidates = [
|
||||
self.page.locator("input[type='text']").first,
|
||||
self.page.locator("input[name*='user' i]").first,
|
||||
self.page.locator("input[name*='username' i]").first,
|
||||
self.page.locator("input[placeholder*='用' i]").first,
|
||||
self.page.get_by_placeholder(re.compile(r"用户名|账号|登录名", re.I)).first,
|
||||
]
|
||||
pass_candidates = [
|
||||
self.page.locator("input[type='password']").first,
|
||||
self.page.locator("input[name*='pass' i]").first,
|
||||
self.page.locator("input[placeholder*='密' i]").first,
|
||||
self.page.get_by_placeholder(re.compile(r"密码", re.I)).first,
|
||||
]
|
||||
|
||||
user_loc = next((c for c in user_candidates if c.count() > 0 and c.is_visible()), None)
|
||||
pass_loc = next((c for c in pass_candidates if c.count() > 0 and c.is_visible()), None)
|
||||
if user_loc is None or pass_loc is None:
|
||||
raise RuntimeError("未能定位到用户名/密码输入框。")
|
||||
|
||||
# 输入用户名密码
|
||||
self._log("[Step1] 正在输入用户名密码...")
|
||||
user_loc.click(timeout=5000, force=True)
|
||||
user_loc.fill(self.username)
|
||||
pass_loc.click(timeout=5000, force=True)
|
||||
pass_loc.fill(self.password)
|
||||
|
||||
# 登录按钮
|
||||
login_btn = self.page.get_by_role("button", name=re.compile(r"(登录|Log in|Sign in|立即登录)", re.I)).first
|
||||
try:
|
||||
if login_btn.count() > 0:
|
||||
login_btn.click(timeout=10000, force=True)
|
||||
else:
|
||||
self.page.locator("button[type='submit']").first.click(timeout=10000, force=True)
|
||||
except Exception:
|
||||
self.page.keyboard.press("Enter")
|
||||
|
||||
# 等待菜单
|
||||
self._log("[Step1] 等待登录完成...")
|
||||
self.page.wait_for_selector("text=工作室运营", timeout=30000)
|
||||
time.sleep(1)
|
||||
|
||||
# 2) 进入源码管理
|
||||
self._log("[Step1] 进入源码管理...")
|
||||
self.page.get_by_text("工作室运营", exact=False).first.click(timeout=10000, force=True)
|
||||
time.sleep(0.8)
|
||||
self.page.get_by_text("源码管理", exact=False).first.click(timeout=10000, force=True)
|
||||
time.sleep(1)
|
||||
|
||||
# 3) 搜索项目
|
||||
self._log("[Step1] 搜索项目...")
|
||||
self.page.wait_for_timeout(1500)
|
||||
|
||||
search_input_candidates = [
|
||||
self.page.locator("input[type='search']").first,
|
||||
self.page.locator("input[placeholder*='搜索' i]").first,
|
||||
self.page.get_by_placeholder("搜索").first,
|
||||
self.page.locator(".el-input__inner").filter(has_text=re.compile(r"搜索", re.I)).first,
|
||||
]
|
||||
search_input = next((c for c in search_input_candidates if c.count() > 0 and c.is_visible()), None)
|
||||
if search_input is None:
|
||||
all_inputs = self.page.locator("input[type='text']").all()
|
||||
for inp in all_inputs:
|
||||
if inp.is_visible():
|
||||
search_input = inp
|
||||
break
|
||||
|
||||
if search_input is None:
|
||||
raise RuntimeError("未能定位到搜索输入框。")
|
||||
|
||||
search_input.click(timeout=5000, force=True)
|
||||
search_input.fill(self.project_name)
|
||||
search_input.press("Enter")
|
||||
|
||||
# 从 project_name 提取项目编号(前6个字符)
|
||||
project_code = self.project_name[:6] if len(self.project_name) >= 6 else self.project_name
|
||||
|
||||
# 等待列表渲染
|
||||
self.page.wait_for_selector(f"text={project_code}", timeout=20000)
|
||||
self.page.wait_for_timeout(1000)
|
||||
|
||||
# 查找项目行
|
||||
row = self.page.locator("tr", has_text=self.project_name).first
|
||||
if row.count() == 0:
|
||||
row = self.page.locator("tr", has_text=project_code).first
|
||||
|
||||
# 点击"查看"按钮
|
||||
view_btn = row.get_by_role("button", name=re.compile(r"(查看|View)", re.I)).first
|
||||
if view_btn.count() == 0 or not view_btn.is_visible():
|
||||
raise RuntimeError("未能定位到'查看'按钮。")
|
||||
|
||||
view_btn.click(timeout=10000)
|
||||
|
||||
# 4) 滚动到底并点击 FeastCoding
|
||||
self._log("[Step1] 查找 FeastCoding 按钮...")
|
||||
self.page.wait_for_timeout(1500)
|
||||
|
||||
feast_loc = self.page.get_by_text(self.feast_button, exact=False).first
|
||||
feast_loc.wait_for(state="visible", timeout=15000)
|
||||
self._log("[Step1] 已找到 FeastCoding 按钮")
|
||||
|
||||
feast_loc.scroll_into_view_if_needed(timeout=10000)
|
||||
self.page.wait_for_timeout(800)
|
||||
|
||||
self._log("[Step1] 点击 FeastCoding 按钮...")
|
||||
feast_loc.click(timeout=10000)
|
||||
self.page.wait_for_timeout(3000)
|
||||
self._log("[Step1] 已完成,内容已复制到剪贴板")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
if self.page is not None:
|
||||
_safe_screenshot(self.page, err_path)
|
||||
raise e
|
||||
finally:
|
||||
if self.browser is not None:
|
||||
try:
|
||||
self.browser.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
"""单独运行 Step1"""
|
||||
config = get_config()
|
||||
chrome_path = config.chrome_path
|
||||
step1_config = config.step1_config
|
||||
|
||||
username = step1_config.get("username", "wangpeng")
|
||||
password = step1_config.get("password", "Feastcoding@123")
|
||||
|
||||
step1 = Step1FeastCoding(chrome_path, username, password)
|
||||
try:
|
||||
step1.run()
|
||||
print("[INFO] Step1 执行成功!")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Step1 执行失败: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user