From 39eacc4c4a24dbbeb67335e8f69a9d46584c52d0 Mon Sep 17 00:00:00 2001 From: chenfuqiang Date: Tue, 15 Sep 2026 10:19:55 +0800 Subject: [PATCH] chore: auto release commit (2026-09-15 10:19:55) --- .env.example | 6 -- CHANGELOG.md | 6 ++ DEMO.md | 2 +- SKILL.md | 2 +- TUTORIAL.md | 4 +- scripts/cli/app.py | 20 +++---- scripts/service/rpa/invoice_rpa.py | 8 +-- scripts/service/rpa/login_handler.py | 83 +++++++++++++--------------- scripts/util/constants.py | 8 +-- 9 files changed, 62 insertions(+), 77 deletions(-) diff --git a/.env.example b/.env.example index 2e39ea3..9030167 100644 --- a/.env.example +++ b/.env.example @@ -4,12 +4,6 @@ OPENCLAW_TEST_TARGET=real_rpa # 可选:mock=本地模拟,simulator_ # 目标网站地址:技能要访问的网站或服务地址 TARGET_BASE_URL=https://sandbox.jc2009.com # 填写完整网址(含 https://);换环境时再改,一般保持默认即可 -# 默认登录账号:仅填写非敏感的账号标识(如工号/登录名/手机号) -DEFAULT_LOGIN_ID= # 留空=按平台自动选用或登记;有值则按该标识筛选;不要填密码 - -# 默认账号编号:仅填账号管理里的纯数字主键(一般留空) -DEFAULT_ACCOUNT_ID= # 留空=自动;只填纯数字;手机号请填上方登录账号 - # 是否显示浏览器窗口:网页自动化时是否弹出可见浏览器 OPENCLAW_BROWSER_HEADLESS=0 # 0=显示窗口(必须,便于介入与排查);1=后台静默(勿作日常用法) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33f86d7..a6f3477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ --- +## 1.0.5 + +- sandbox 登录改为人工输入模式:不再通过 CLI 传入用户名密码,由用户在浏览器中手动完成登录 +- 移除所有硬编码凭据(手机号、密码),从 constants.py 和 .env.example 中清除 +- CLI 命令精简:extract-invoices / test-rpa-connection 只需 --profile-dir 参数 + ## 1.0.4 - RPA 流程优化:轮询间隔 5s→1s,填表改用 fill(),去掉冗余等待,整体提速约 3-5 倍 diff --git a/DEMO.md b/DEMO.md index 93bec4a..880d5ee 100644 --- a/DEMO.md +++ b/DEMO.md @@ -19,7 +19,7 @@ demo_video_url: "" ```powershell $env:PYTHONPATH="D:\jiangchang\huashang\jiangchang-platform-kit\jiangchang-platform-kit\src" cd D:\jiangchang\huashang\verify-invoice-default\verify-invoice-default -python scripts\main.py extract-invoices --username 15527406659 --password qwer1234 --profile-dir "$env:TEMP\verify-invoice-edge-profile" +python scripts\main.py extract-invoices --profile-dir "$env:TEMP\verify-invoice-edge-profile" ``` ## 没有视频时 diff --git a/SKILL.md b/SKILL.md index cc865a0..7151c38 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,7 +1,7 @@ --- name: 票据合规预审助手 description: "高校报销场景的发票合规性秒级预审:核对字段完整性、购买方税号白名单、开票日期与出差行程逻辑、住宿交通经费标准,对连号、同商家集中开票、节假日开票等风险点红黄灯预警,生成《初审意见单》。" -version: 1.0.4 +version: 1.0.5 author: 深圳匠厂科技有限公司 metadata: openclaw: diff --git a/TUTORIAL.md b/TUTORIAL.md index 604d66e..9e8602c 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -25,7 +25,7 @@ $env:PYTHONPATH="D:\jiangchang\huashang\jiangchang-platform-kit\jiangchang-platform-kit\src" cd D:\jiangchang\huashang\verify-invoice-default\verify-invoice-default -python scripts\main.py extract-invoices --username 15527406659 --password qwer1234 --profile-dir "$env:TEMP\verify-invoice-edge-profile" +python scripts\main.py extract-invoices --profile-dir "$env:TEMP\verify-invoice-edge-profile" ``` 参数说明: @@ -41,7 +41,7 @@ python scripts\main.py extract-invoices --username 15527406659 --password qwer12 ### 测试连接 ```powershell -python scripts\main.py test-rpa-connection --username 15527406659 --password qwer1234 --profile-dir "$env:TEMP\verify-invoice-edge-profile" +python scripts\main.py test-rpa-connection --profile-dir "$env:TEMP\verify-invoice-edge-profile" ``` ### 票据合规预审 diff --git a/scripts/cli/app.py b/scripts/cli/app.py index b024cec..930b542 100644 --- a/scripts/cli/app.py +++ b/scripts/cli/app.py @@ -141,8 +141,8 @@ def _handle_list_holidays(args: argparse.Namespace) -> int: def _handle_extract_invoices(args: argparse.Namespace) -> int: return cmd_extract_invoices( - username=str(args.username or "").strip(), - password=str(args.password or "").strip(), + username="", + password="", profile_dir=str(args.profile_dir or "").strip(), headless=bool(args.headless), detail_mode=bool(args.detail_mode), @@ -151,8 +151,8 @@ def _handle_extract_invoices(args: argparse.Namespace) -> int: def _handle_test_rpa_connection(args: argparse.Namespace) -> int: return cmd_test_rpa_connection( - username=str(args.username or "").strip(), - password=str(args.password or "").strip(), + username="", + password="", profile_dir=str(args.profile_dir or "").strip(), ) @@ -192,9 +192,9 @@ def _print_full_usage() -> None: print(" python main.py list-holidays [--year YYYY]") print(" python main.py stats") print(" python main.py run [target [input_id]] [-t target] [-i input_id]") - print(" python main.py extract-invoices --username U --password P --profile-dir DIR") + print(" python main.py extract-invoices --profile-dir DIR") print(" [--headless] [--detail-mode]") - print(" python main.py test-rpa-connection --username U --password P --profile-dir DIR") + print(" python main.py test-rpa-connection --profile-dir DIR") print(" python main.py health") print(" python main.py config-path") print(" python main.py version") @@ -288,17 +288,13 @@ def build_parser() -> ZhArgumentParser: sp.set_defaults(handler=lambda _a: cmd_stats()) # RPA commands - sp = sub.add_parser("extract-invoices", help="从网站提取发票信息") - sp.add_argument("--username", required=True, metavar="用户名") - sp.add_argument("--password", required=True, metavar="密码") + sp = sub.add_parser("extract-invoices", help="从网站提取发票信息(sandbox 需手动登录)") sp.add_argument("--profile-dir", required=True, metavar="浏览器配置目录", dest="profile_dir") sp.add_argument("--headless", action="store_true", help="无头模式(生产不建议)") sp.add_argument("--detail-mode", action="store_true", help="提取详细信息", dest="detail_mode") sp.set_defaults(handler=_handle_extract_invoices) - sp = sub.add_parser("test-rpa-connection", help="测试RPA连接和登录") - sp.add_argument("--username", required=True, metavar="用户名") - sp.add_argument("--password", required=True, metavar="密码") + sp = sub.add_parser("test-rpa-connection", help="测试RPA连接(sandbox 需手动登录)") sp.add_argument("--profile-dir", required=True, metavar="浏览器配置目录", dest="profile_dir") sp.set_defaults(handler=_handle_test_rpa_connection) diff --git a/scripts/service/rpa/invoice_rpa.py b/scripts/service/rpa/invoice_rpa.py index ef08923..65b2abd 100644 --- a/scripts/service/rpa/invoice_rpa.py +++ b/scripts/service/rpa/invoice_rpa.py @@ -70,9 +70,9 @@ async def _start_browser(profile_dir: str, headless: bool = False) -> tuple[Any, @rpa_step("登录 sandbox 门户") -async def _login_sandbox_portal(page: Any, phone: str, password: str) -> bool: - """登录 sandbox.jc2009.com 门户。""" - return await login_sandbox(page, phone, password) +async def _login_sandbox_portal(page: Any) -> bool: + """登录 sandbox.jc2009.com 门户(人工输入)。""" + return await login_sandbox(page) @rpa_step("进入智慧校园") @@ -152,7 +152,7 @@ async def run_invoice_rpa( pw, context, page = await _start_browser(profile_dir, headless) # 步骤 2:登录 sandbox 门户 - sandbox_ok = await _login_sandbox_portal(page, username, password) + sandbox_ok = await _login_sandbox_portal(page) if not sandbox_ok: return InvoiceRpaResult( success=False, diff --git a/scripts/service/rpa/login_handler.py b/scripts/service/rpa/login_handler.py index 6c1c8eb..08135fa 100644 --- a/scripts/service/rpa/login_handler.py +++ b/scripts/service/rpa/login_handler.py @@ -18,8 +18,7 @@ from jiangchang_skill_core.activity import interruptible_sleep logger = logging.getLogger(__name__) # ── Sandbox 门户登录配置 ── -SANDBOX_LOGIN_PHONE = "15527406659" -SANDBOX_LOGIN_PASSWORD = "qwer1234" +SANDBOX_WAIT_TIMEOUT_SEC = 180 # 等待人工完成 sandbox 登录的最长时间 # ── 智慧校园默认凭据 ── SMART_CAMPUS_DEFAULT_USER = "demo" @@ -35,13 +34,10 @@ async def login_if_needed( page: Any, username: str, password: str, - wait_sec: int = 120, + wait_sec: int = 180, ) -> bool: """检查登录状态,如需要则执行登录。""" - phone = username or SANDBOX_LOGIN_PHONE - pwd = password or SANDBOX_LOGIN_PASSWORD - - sandbox_ok = await login_sandbox(page, phone, pwd) + sandbox_ok = await login_sandbox(page, wait_sec=wait_sec) if not sandbox_ok: return False @@ -80,60 +76,46 @@ async def _poll_until(check_fn, timeout_sec: float = 30.0) -> bool: # ── 阶段一:登录 sandbox.jc2009.com ── -async def login_sandbox(page: Any, phone: str, password: str) -> bool: - """登录 sandbox.jc2009.com 门户。已登录则直接返回 True。""" - logger.info("sandbox_login_start phone=%s***", phone[:3]) +async def login_sandbox(page: Any, wait_sec: int = SANDBOX_WAIT_TIMEOUT_SEC) -> bool: + """登录 sandbox.jc2009.com 门户。 + + - 已登录(有智慧校园/Sign out/首页 URL)→ 直接返回 True + - 未登录 → 打开登录页,提示用户在浏览器中手动完成登录,轮询等待 + """ + logger.info("sandbox_login_start") try: - if _is_sandbox_home(page.url) or await page.get_by_role("button", name=re.compile(r"Sign out|退出")).count() > 0 or await page.get_by_text("智慧校园", exact=False).count() > 0: + # 1. 检查是否已登录 + if await _is_sandbox_logged_in(page): logger.info("sandbox_already_logged_in url=%s", page.url) return True - # 找登录表单 - phone_input = page.get_by_role("textbox", name=re.compile(r"Phone|phone|手机")) - if await phone_input.count() == 0: + # 2. 打开 sandbox 首页 + if await page.get_by_role("textbox", name=re.compile(r"Phone|phone|手机")).count() == 0: await page.goto("https://sandbox.jc2009.com/", wait_until="domcontentloaded", timeout=NAVIGATION_TIMEOUT_MS) - # 用轮询替代固定等待 - async def _home_check(): - return _is_sandbox_home(page.url) or await page.get_by_text("智慧校园", exact=False).count() > 0 - await _poll_until(_home_check, timeout_sec=10.0) - if _is_sandbox_home(page.url) or await page.get_by_text("智慧校园", exact=False).count() > 0: + await asyncio.sleep(2.0) + if await _is_sandbox_logged_in(page): logger.info("sandbox_already_logged_in_after_redirect") return True - phone_input = page.get_by_role("textbox", name=re.compile(r"Phone|phone|手机")) - if await phone_input.count() == 0: - logger.warning("sandbox_login_page_not_found url=%s", page.url) - return False + # 3. 提示用户手动登录 + print("\n" + "=" * 50) + print("请在浏览器中手动完成 sandbox 登录") + print(f"登录地址:{page.url}") + print(f"最多等待 {wait_sec} 秒...") + print("=" * 50 + "\n") + logger.info("sandbox_waiting_for_human_login url=%s timeout=%ss", page.url, wait_sec) - logger.info("sandbox_login_page_detected") - - # 输入手机号(fill 直接设值,比 type 快) - await _type_field(phone_input, phone) - # 输入密码 - pwd_input = page.get_by_role("textbox", name=re.compile(r"Password|password|密码")) - if await pwd_input.count() == 0: - pwd_input = page.get_by_label("Password") - await _type_field(pwd_input, password) - - # 点击 Sign in - sign_in_btn = page.get_by_role("button", name=re.compile(r"Sign in|登录")) - await sign_in_btn.click() - logger.info("sandbox_login_click_sign_in") - - # 快速轮询等待登录成功 + # 4. 轮询等待登录完成 async def _login_done(): - return ( - _is_sandbox_home(page.url) - or await page.get_by_role("button", name=re.compile(r"Sign out|退出")).count() > 0 - or await page.get_by_text("智慧校园", exact=False).count() > 0 - ) + return await _is_sandbox_logged_in(page) - if await _poll_until(_login_done, timeout_sec=30.0): + if await _poll_until(_login_done, timeout_sec=float(wait_sec)): logger.info("sandbox_login_success url=%s", page.url) return True logger.warning("sandbox_login_timeout") + print("❌ sandbox 登录超时,请重新运行。") return False except Exception as e: @@ -141,6 +123,17 @@ async def login_sandbox(page: Any, phone: str, password: str) -> bool: return False +async def _is_sandbox_logged_in(page: Any) -> bool: + """快速检查是否已在 sandbox 首页(已登录)。""" + if _is_sandbox_home(page.url): + return True + if await page.get_by_role("button", name=re.compile(r"Sign out|退出")).count() > 0: + return True + if await page.get_by_text("智慧校园", exact=False).count() > 0: + return True + return False + + # ── 阶段二:进入智慧校园 ── diff --git a/scripts/util/constants.py b/scripts/util/constants.py index 727a64b..bb5e286 100644 --- a/scripts/util/constants.py +++ b/scripts/util/constants.py @@ -8,7 +8,7 @@ from jiangchang_skill_core import config # ── 技能标识 ── SKILL_SLUG = "verify-invoice-default" -SKILL_VERSION = "1.0.4" +SKILL_VERSION = "1.0.5" LOG_LOGGER_NAME = "openclaw.skill.verify_invoice_default" PLATFORM_KIT_MIN_VERSION = "1.2.11" @@ -17,11 +17,7 @@ TARGET_PLATFORM = "jc2009" LEASE_HOLDER = "verify-invoice-default" LEASE_TTL_SEC = "1800" -# ── Sandbox 登录凭据 ── -SANDBOX_LOGIN_PHONE = "15527406659" -SANDBOX_LOGIN_PASSWORD = "qwer1234" - -# ── 智慧校园默认凭据 ── +# ── 智慧校园默认凭据(sandbox 平台内置 demo 账号) ── SMART_CAMPUS_DEFAULT_USER = "demo" SMART_CAMPUS_DEFAULT_PASSWORD = "demo123"