style: 统一 Ruff 代码格式

This commit is contained in:
yml2213
2026-08-30 21:04:52 +08:00
parent c891ac982e
commit 47e19ed7b2
90 changed files with 5574 additions and 2350 deletions
+274 -81
View File
@@ -5,6 +5,7 @@ The worker owns per-job sessions and invokes the already verified protocol
scripts. It intentionally exposes QR images and state only; cookies and raw
payment links never leave the worker API.
"""
from __future__ import annotations
import argparse
@@ -79,15 +80,23 @@ def _safe_log(job: dict, line: str) -> None:
clean = re.sub(r"weixin://\S+", "[付款链接已隐藏]", line)
clean = re.sub(r"HOLD_[A-Za-z0-9_-]+", "[订单已隐藏]", clean)
clean = re.sub(r"(订单\s*[:]\s*)\S+", r"\1[订单已隐藏]", clean)
clean = re.sub(r"(?:token|openid|openkey|cookie)=\S+", "[敏感字段已隐藏]", clean, flags=re.I)
clean = re.sub(r"(?:pay_token|web_token|anti_token|session_id|sessionid)\s*[:= ]\s*\S+",
"[敏感字段已隐藏]", clean, flags=re.I)
clean = re.sub(
r"(?:token|openid|openkey|cookie)=\S+", "[敏感字段已隐藏]", clean, flags=re.I
)
clean = re.sub(
r"(?:pay_token|web_token|anti_token|session_id|sessionid)\s*[:= ]\s*\S+",
"[敏感字段已隐藏]",
clean,
flags=re.I,
)
timestamp = time.strftime("%H:%M:%S")
with _lock:
job["logs"] = (job.get("logs", []) + [f"[{timestamp}] {clean.strip()}"])[-100:]
def _run_process(job_id: str, command: list[str], phase: str, mark_success: bool = True) -> int:
def _run_process(
job_id: str, command: list[str], phase: str, mark_success: bool = True
) -> int:
"""Run one stage process and return its exit code.
When ``mark_success`` is False the caller owns the post-success state
@@ -104,16 +113,24 @@ def _run_process(job_id: str, command: list[str], phase: str, mark_success: bool
stage_started = time.monotonic()
_safe_log(job, f"[{phase}] 开始执行")
try:
process = subprocess.Popen(command, cwd=ROOT, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, text=True,
bufsize=1, env=environment)
process = subprocess.Popen(
command,
cwd=ROOT,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
env=environment,
)
with _lock:
job["process_pid"] = process.pid
assert process.stdout is not None
for line in process.stdout:
_safe_log(job, line)
code = process.wait()
_safe_log(job, f"[{phase}] 执行结束,耗时 {time.monotonic() - stage_started:.1f}")
_safe_log(
job, f"[{phase}] 执行结束,耗时 {time.monotonic() - stage_started:.1f}"
)
with _lock:
job["process_pid"] = None
if code != 0:
@@ -134,7 +151,9 @@ def _run_process(job_id: str, command: list[str], phase: str, mark_success: bool
job["message"] = "付款流程已完成"
return code
except Exception as exc: # noqa: BLE001
_safe_log(job, f"[{phase}] 执行异常,耗时 {time.monotonic() - stage_started:.1f}")
_safe_log(
job, f"[{phase}] 执行异常,耗时 {time.monotonic() - stage_started:.1f}"
)
with _lock:
job["status"] = "failed"
job["message"] = str(exc)
@@ -146,42 +165,74 @@ def _start_login(job_id: str, provider: str, timeout: int) -> None:
directory = _job_dir(job_id)
session = directory / "mall-session.json"
qr = directory / ("qq-login.jpg" if provider == "qq" else "wechat-login.jpg")
command = [sys.executable, f"scripts/login-{provider}.py", "--session", str(session),
"--qr", str(qr), "--timeout", str(timeout)]
command = [
sys.executable,
f"scripts/login-{provider}.py",
"--session",
str(session),
"--qr",
str(qr),
"--timeout",
str(timeout),
]
with _lock:
job["provider"] = provider
job["qr_path"] = str(qr)
job["session_path"] = str(session)
job["status"] = "waiting_login"
job["phase"] = "login"
threading.Thread(target=_run_process, args=(job_id, command, "login"), daemon=True).start()
threading.Thread(
target=_run_process, args=(job_id, command, "login"), daemon=True
).start()
def _selection_options(job_id: str, platform: str, points: int | None, zone_id: str | None = None) -> dict:
def _selection_options(
job_id: str, platform: str, points: int | None, zone_id: str | None = None
) -> dict:
if job_id not in _jobs:
raise ValueError("任务不存在")
selector = _load_selector()
session = json.loads((_job_dir(job_id) / "mall-session.json").read_text(encoding="utf-8"))
session = json.loads(
(_job_dir(job_id) / "mall-session.json").read_text(encoding="utf-8")
)
cookies = session.get("cookies", {})
products = selector.product_options(cookies, platform)
if points is not None and not any(int(item["points"]) == points for item in products):
if points is not None and not any(
int(item["points"]) == points for item in products
):
raise ValueError("当前登录态不支持该点券档位")
product = next((item for item in products if int(item["points"]) == points), None) if points else None
product = (
next((item for item in products if int(item["points"]) == points), None)
if points
else None
)
if product is None:
product = products[0]
cmall = selector.Cmall(cookies, str(product["offer_id"]), platform)
zones = cmall.zones()
selected_zone = next((zone for zone in zones if str(zone["zone_id"]) == str(zone_id)), None)
selected_zone = next(
(zone for zone in zones if str(zone["zone_id"]) == str(zone_id)), None
)
if zone_id and selected_zone is None:
raise ValueError("区服不存在")
selected_zone = selected_zone or (zones[0] if zones else None)
roles = cmall.roles(selected_zone["zone_id"]) if selected_zone else []
return {"products": products, "zones": zones, "roles": roles,
"default_product": product, "default_zone": selected_zone}
return {
"products": products,
"zones": zones,
"roles": roles,
"default_product": product,
"default_zone": selected_zone,
}
def _payment_stage(job_id: str, command: list[str], phase: str, running_message: str,
failed_message: str) -> bool:
def _payment_stage(
job_id: str,
command: list[str],
phase: str,
running_message: str,
failed_message: str,
) -> bool:
"""Run one payment stage; return True on success without touching final state."""
job = _jobs[job_id]
with _lock:
@@ -210,18 +261,32 @@ def _check_payment_once(job_id: str) -> int:
"""Read-only completion check; returns 0=confirmed, 1=not yet, 2=check failed."""
job = _jobs[job_id]
directory = _job_dir(job_id)
command = [sys.executable, "scripts/jsdom-pay.py", "--check-only",
"--session", str(directory / "mall-session.json"),
"--out-dir", str(directory / "jsdom-order")]
command = [
sys.executable,
"scripts/jsdom-pay.py",
"--check-only",
"--session",
str(directory / "mall-session.json"),
"--out-dir",
str(directory / "jsdom-order"),
]
environment = os.environ.copy()
environment.pop("NODE_OPTIONS", None)
check_started = time.monotonic()
_safe_log(job, "[到账检测] 开始执行")
try:
result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True,
env=environment, timeout=90)
result = subprocess.run(
command,
cwd=ROOT,
capture_output=True,
text=True,
env=environment,
timeout=90,
)
except subprocess.TimeoutExpired:
_safe_log(job, f"[到账检测] 执行超时,耗时 {time.monotonic() - check_started:.1f}")
_safe_log(
job, f"[到账检测] 执行超时,耗时 {time.monotonic() - check_started:.1f}"
)
with _lock:
job["payment_last_checked_at"] = int(time.time())
return 2
@@ -229,7 +294,9 @@ def _check_payment_once(job_id: str) -> int:
_safe_log(job, line)
for line in (result.stderr or "").splitlines():
_safe_log(job, line)
_safe_log(job, f"[到账检测] 执行结束,耗时 {time.monotonic() - check_started:.1f}")
_safe_log(
job, f"[到账检测] 执行结束,耗时 {time.monotonic() - check_started:.1f}"
)
with _lock:
job["payment_last_checked_at"] = int(time.time())
return result.returncode
@@ -275,28 +342,62 @@ def _payment_flow(job_id: str, selection: dict) -> None:
job["status"] = "ordering"
job["phase"] = "payment"
job["message"] = "正在创建商城订单"
order_cmd = [sys.executable, "main.py", "mall", "auto", "--session", str(session),
"--order-template", str(ROOT / "config" / "mall-order-template.json"),
"--quantity", "1", "--product-id", str(selection["product_id"]),
"--offer-id", str(selection["offer_id"]),
"--role-id", str(selection["role_id"]), "--role-name", str(selection["role_name"]),
"--zone-id", str(selection["zone_id"]), "--zone-name", str(selection["zone_name"]),
"--output", str(response)]
order_cmd = [
sys.executable,
"main.py",
"mall",
"auto",
"--session",
str(session),
"--order-template",
str(ROOT / "config" / "mall-order-template.json"),
"--quantity",
"1",
"--product-id",
str(selection["product_id"]),
"--offer-id",
str(selection["offer_id"]),
"--role-id",
str(selection["role_id"]),
"--role-name",
str(selection["role_name"]),
"--zone-id",
str(selection["zone_id"]),
"--zone-name",
str(selection["zone_name"]),
"--output",
str(response),
]
if selection.get("area"):
order_cmd.extend(["--area", str(selection["area"])])
if selection.get("partition"):
order_cmd.extend(["--partition", str(selection["partition"])])
if selection.get("order_pf"):
order_cmd.extend(["--pf", str(selection["order_pf"])])
if not _payment_stage(job_id, order_cmd, "order", "正在创建商城订单", "创建商城订单失败"):
if not _payment_stage(
job_id, order_cmd, "order", "正在创建商城订单", "创建商城订单失败"
):
return
pay_cmd = [sys.executable, "scripts/jsdom-pay.py", "--session", str(session),
"--mall-response", str(response), "--out-dir", str(output),
"--zone-id", str(selection["zone_id"]),
"--pf", str(selection.get("order_pf", "")),
"--amount-fen", str(selection["price_fen"]),
"--skip-payment-check"]
if not _payment_stage(job_id, pay_cmd, "payment", "正在生成微信付款码", "生成付款码失败"):
pay_cmd = [
sys.executable,
"scripts/jsdom-pay.py",
"--session",
str(session),
"--mall-response",
str(response),
"--out-dir",
str(output),
"--zone-id",
str(selection["zone_id"]),
"--pf",
str(selection.get("order_pf", "")),
"--amount-fen",
str(selection["price_fen"]),
"--skip-payment-check",
]
if not _payment_stage(
job_id, pay_cmd, "payment", "正在生成微信付款码", "生成付款码失败"
):
return
meta_path = output / "payment-meta.json"
try:
@@ -318,7 +419,9 @@ def _start_payment(job_id: str, selection: dict) -> None:
_jobs[job_id]["status"] = "ordering"
_jobs[job_id]["phase"] = "payment"
_jobs[job_id]["message"] = "正在创建商城订单"
threading.Thread(target=_payment_flow, args=(job_id, selection), daemon=True).start()
threading.Thread(
target=_payment_flow, args=(job_id, selection), daemon=True
).start()
def _stop_job(job_id: str) -> None:
@@ -344,13 +447,18 @@ def _stop_job(job_id: str) -> None:
def _public_job(job_id: str) -> dict:
job = _jobs[job_id]
result = {key: value for key, value in job.items()
if key not in {"directory", "session_path", "process_pid"}}
result = {
key: value
for key, value in job.items()
if key not in {"directory", "session_path", "process_pid"}
}
qr_path = job.get("qr_path", "")
if qr_path and Path(qr_path).exists():
qr_bytes = Path(qr_path).read_bytes()
result["qr_data"] = base64.b64encode(qr_bytes).decode("ascii")
result["qr_mime_type"] = "image/jpeg" if qr_bytes.startswith(b"\xff\xd8\xff") else "image/png"
result["qr_mime_type"] = (
"image/jpeg" if qr_bytes.startswith(b"\xff\xd8\xff") else "image/png"
)
output = Path(job["directory"]) / "jsdom-order"
for name in ("wechat-pay.png", "payment-status.json"):
path = None
@@ -359,18 +467,32 @@ def _public_job(job_id: str) -> dict:
path = direct if direct.exists() else next(output.glob(f"*/{name}"), None)
if path and name.endswith(".png"):
payment_qr_bytes = path.read_bytes()
result["payment_qr_data"] = base64.b64encode(payment_qr_bytes).decode("ascii")
result["payment_qr_mime_type"] = "image/jpeg" if payment_qr_bytes.startswith(b"\xff\xd8\xff") else "image/png"
result["payment_qr_data"] = base64.b64encode(payment_qr_bytes).decode(
"ascii"
)
result["payment_qr_mime_type"] = (
"image/jpeg"
if payment_qr_bytes.startswith(b"\xff\xd8\xff")
else "image/png"
)
elif path:
try:
status = json.loads(path.read_text(encoding="utf-8"))
matched = status.get("matched_completion") if isinstance(status, dict) else None
matched = (
status.get("matched_completion")
if isinstance(status, dict)
else None
)
result["payment_status"] = {
"checked_at": status.get("checked_at") if isinstance(status, dict) else None,
"checked_at": status.get("checked_at")
if isinstance(status, dict)
else None,
"matched_completion": {
"is_finished": matched.get("is_finished"),
"status": matched.get("status"),
} if isinstance(matched, dict) else None,
}
if isinstance(matched, dict)
else None,
}
except (OSError, json.JSONDecodeError):
pass
@@ -411,8 +533,14 @@ class Handler(BaseHTTPRequestHandler):
directory = DEFAULT_DATA / job_id
directory.mkdir(parents=True, exist_ok=True)
os.chmod(directory, 0o700)
_jobs[job_id] = {"job_id": job_id, "status": "created", "phase": "login",
"logs": [], "directory": str(directory), "created_at": int(time.time())}
_jobs[job_id] = {
"job_id": job_id,
"status": "created",
"phase": "login",
"logs": [],
"directory": str(directory),
"created_at": int(time.time()),
}
return self._json(201, _public_job(job_id))
if len(path) == 4 and path[:2] == ["v1", "jobs"] and path[3] == "login":
job_id = path[2]
@@ -421,44 +549,87 @@ class Handler(BaseHTTPRequestHandler):
return self._json(400, {"detail": "无效任务或登录方式"})
_start_login(job_id, body["provider"], int(body.get("timeout", 600)))
return self._json(202, _public_job(job_id))
if len(path) == 4 and path[:2] == ["v1", "jobs"] and path[3] == "selection-options":
if (
len(path) == 4
and path[:2] == ["v1", "jobs"]
and path[3] == "selection-options"
):
job_id = path[2]
body = self._body()
options = _selection_options(job_id, str(body.get("platform", "android")), body.get("points"), body.get("zone_id"))
options = _selection_options(
job_id,
str(body.get("platform", "android")),
body.get("points"),
body.get("zone_id"),
)
_jobs[job_id]["selection_options"] = options
return self._json(200, options)
if len(path) == 4 and path[:2] == ["v1", "jobs"] and path[3] == "selection":
job_id = path[2]
body = self._body()
required = ("platform", "points", "product_id", "role_id", "role_name", "zone_id")
required = (
"platform",
"points",
"product_id",
"role_id",
"role_name",
"zone_id",
)
if job_id not in _jobs or any(not body.get(key) for key in required):
return self._json(400, {"detail": "选择参数不完整"})
selector = _load_selector()
if body["platform"] not in selector.PLATFORMS:
return self._json(400, {"detail": "不支持的平台"})
session_path = _job_dir(job_id) / "mall-session.json"
cookies = json.loads(session_path.read_text(encoding="utf-8")).get("cookies", {})
product = next((item for item in selector.product_options(cookies, body["platform"])
if str(item["product_id"]) == str(body["product_id"])
and int(item["points"]) == int(body["points"])), None)
cookies = json.loads(session_path.read_text(encoding="utf-8")).get(
"cookies", {}
)
product = next(
(
item
for item in selector.product_options(cookies, body["platform"])
if str(item["product_id"]) == str(body["product_id"])
and int(item["points"]) == int(body["points"])
),
None,
)
if product is None:
return self._json(400, {"detail": "商品已失效,请重新选择"})
cmall = selector.Cmall(cookies, str(product["offer_id"]), body["platform"])
zone = next((item for item in cmall.zones()
if str(item["zone_id"]) == str(body["zone_id"])), None)
cmall = selector.Cmall(
cookies, str(product["offer_id"]), body["platform"]
)
zone = next(
(
item
for item in cmall.zones()
if str(item["zone_id"]) == str(body["zone_id"])
),
None,
)
if zone is None:
return self._json(400, {"detail": "区服已失效,请重新选择"})
role = next((item for item in cmall.roles(zone["zone_id"])
if str(item["role_id"]) == str(body["role_id"])), None)
role = next(
(
item
for item in cmall.roles(zone["zone_id"])
if str(item["role_id"]) == str(body["role_id"])
),
None,
)
if role is None or role.get("ban_status") == "1":
return self._json(400, {"detail": "角色不可充值,请重新选择"})
_jobs[job_id]["selection"] = {
"platform": body["platform"], "points": product["points"],
"platform": body["platform"],
"points": product["points"],
"price_fen": product["price_fen"],
"product_id": product["product_id"], "offer_id": product["offer_id"],
"zone_id": zone["zone_id"], "zone_name": zone["name"],
"role_id": role["role_id"], "role_name": role["name"],
"area": role.get("area", ""), "partition": role.get("partition", ""),
"product_id": product["product_id"],
"offer_id": product["offer_id"],
"zone_id": zone["zone_id"],
"zone_name": zone["name"],
"role_id": role["role_id"],
"role_name": role["name"],
"area": role.get("area", ""),
"partition": role.get("partition", ""),
"order_pf": selector.PLATFORMS[body["platform"]]["order_pf"],
}
_jobs[job_id]["phase"] = "payment"
@@ -473,14 +644,23 @@ class Handler(BaseHTTPRequestHandler):
with _lock:
if job.get("status") == "stopped":
return self._json(400, {"detail": "任务已停止,不能生成付款码"})
if job.get("status") in {"ordering", "waiting_payment", "payment_timeout"} \
or job.get("process_pid"):
return self._json(400, {"detail": "已有进行中的支付流程,请勿重复操作"})
if job.get("status") in {
"ordering",
"waiting_payment",
"payment_timeout",
} or job.get("process_pid"):
return self._json(
400, {"detail": "已有进行中的支付流程,请勿重复操作"}
)
if not job.get("selection"):
return self._json(400, {"detail": "请先完成角色选择"})
_start_payment(job_id, job["selection"])
return self._json(202, _public_job(job_id))
if len(path) == 5 and path[:2] == ["v1", "jobs"] and path[3:5] == ["payment", "check"]:
if (
len(path) == 5
and path[:2] == ["v1", "jobs"]
and path[3:5] == ["payment", "check"]
):
job_id = path[2]
if job_id not in _jobs:
return self._json(400, {"detail": "任务不存在"})
@@ -541,7 +721,9 @@ def _restore_jobs(data_dir: Path) -> int:
continue
if not meta_path.exists():
_jobs[job_id] = {
"job_id": job_id, "status": "ready", "phase": "selection",
"job_id": job_id,
"status": "ready",
"phase": "selection",
"logs": ["服务重启,已从任务目录恢复登录会话"],
"directory": str(directory),
"created_at": int(time.time()),
@@ -569,7 +751,9 @@ def _restore_jobs(data_dir: Path) -> int:
except (OSError, json.JSONDecodeError):
pass
_jobs[job_id] = {
"job_id": job_id, "status": status, "phase": phase,
"job_id": job_id,
"status": status,
"phase": phase,
"logs": ["服务重启,已从任务目录恢复本任务"],
"directory": str(directory),
"created_at": int(time.time()),
@@ -587,9 +771,15 @@ def main() -> int:
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=8810)
parser.add_argument("--data-dir", type=Path, default=DEFAULT_DATA)
parser.add_argument("--evidence-ttl-hours", type=int, default=DEFAULT_EVIDENCE_TTL_HOURS,
help="任务原始证据保留时长;0 表示不自动清理")
parser.add_argument("--key", default=None, help="HTTP Bearer 鉴权密钥;默认读取 YYB_WORKER_KEY")
parser.add_argument(
"--evidence-ttl-hours",
type=int,
default=DEFAULT_EVIDENCE_TTL_HOURS,
help="任务原始证据保留时长;0 表示不自动清理",
)
parser.add_argument(
"--key", default=None, help="HTTP Bearer 鉴权密钥;默认读取 YYB_WORKER_KEY"
)
args = parser.parse_args()
# 强制绝对路径:子进程以 ROOT 为 cwd,相对路径会让任务文件写到错误位置。
DEFAULT_DATA = Path(args.data_dir).resolve()
@@ -603,7 +793,10 @@ def main() -> int:
os.chmod(DEFAULT_DATA, 0o700)
removed = _cleanup_expired_jobs(DEFAULT_DATA, args.evidence_ttl_hours)
if removed:
print(f"已清理 {removed} 个过期任务证据(保留期 {args.evidence_ttl_hours} 小时)", flush=True)
print(
f"已清理 {removed} 个过期任务证据(保留期 {args.evidence_ttl_hours} 小时)",
flush=True,
)
restored = _restore_jobs(DEFAULT_DATA)
if restored:
print(f"已从任务目录恢复 {restored} 个支付任务", flush=True)