feat: 短信验证码登录 + v4 人机协作方案
核心改动: - weidian_sso_login_v4.py: 全新人机协作登录方案 - Playwright 打开页面 + 自动填手机号 - 人拖滑块(唯一需要人做的事) - 脚本自动拦截 ticket → 发短信 - 人输入验证码 → 自动提交 → 保存 auth - 反检测: 隐藏 webdriver 标记、模拟 iPhone 设备、逐字输入 - 多 selector 兼容(微店不同版本 DOM 结构) - 自动截图 debug(失败时) - auth_service.py: 重写,集成 v4 方案 - login_with_password(): 密码登录(全自动) - login_with_sms(): 短信登录(人机协作) - 保存 Playwright storage_state + 精简 cookies JSON - accounts.py 路由: 新增 /login_sms/<id> 接口 - 密码登录和短信登录两条路径 - 状态轮询支持新的交互状态 - accounts.html 模板: - 新增「短信登录」按钮 - 确认弹窗提醒用户需要浏览器交互
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
import asyncio
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from flask import Blueprint, request, jsonify, render_template
|
||||
from server.database import get_db
|
||||
from server.services.auth_service import get_auth_path, has_auth, login_with_password
|
||||
from server.services.auth_service import (
|
||||
get_auth_path, has_auth, login_with_password, login_with_sms
|
||||
)
|
||||
|
||||
bp = Blueprint('accounts', __name__, url_prefix='/accounts')
|
||||
|
||||
@@ -22,13 +25,13 @@ def add_account():
|
||||
phone = request.form.get('phone', '').strip()
|
||||
password = request.form.get('password', '').strip()
|
||||
|
||||
if not name or not phone or not password:
|
||||
return jsonify(success=False, msg='请填写完整信息'), 400
|
||||
if not name or not phone:
|
||||
return jsonify(success=False, msg='请填写名称和手机号'), 400
|
||||
|
||||
db = get_db()
|
||||
cursor = db.execute(
|
||||
'INSERT INTO accounts (name, phone, password, auth_file, login_msg) VALUES (?, ?, ?, ?, ?)',
|
||||
(name, phone, password, '', '登录中...')
|
||||
(name, phone, password, '', '待登录')
|
||||
)
|
||||
account_id = cursor.lastrowid
|
||||
auth_file = get_auth_path(account_id)
|
||||
@@ -36,9 +39,6 @@ def add_account():
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
# 后台异步登录
|
||||
_start_bg_login(account_id, phone, password)
|
||||
|
||||
return jsonify(success=True, id=account_id)
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ def delete_account(account_id):
|
||||
|
||||
@bp.route('/login/<int:account_id>', methods=['POST'])
|
||||
def do_login(account_id):
|
||||
"""用 Playwright 模拟浏览器自动登录微店"""
|
||||
"""密码登录(自动,无需人参与)"""
|
||||
db = get_db()
|
||||
account = db.execute('SELECT * FROM accounts WHERE id = ?', (account_id,)).fetchone()
|
||||
db.close()
|
||||
@@ -74,7 +74,8 @@ def do_login(account_id):
|
||||
# 标记为登录中
|
||||
db = get_db()
|
||||
db.execute(
|
||||
"UPDATE accounts SET is_logged_in = 0, login_msg = '登录中...', updated_at = datetime('now','localtime') WHERE id = ?",
|
||||
"UPDATE accounts SET is_logged_in = 0, login_msg = '登录中...', "
|
||||
"updated_at = datetime('now','localtime') WHERE id = ?",
|
||||
(account_id,)
|
||||
)
|
||||
db.commit()
|
||||
@@ -86,6 +87,39 @@ def do_login(account_id):
|
||||
return jsonify(success=True, msg='登录中...')
|
||||
|
||||
|
||||
@bp.route('/login_sms/<int:account_id>', methods=['POST'])
|
||||
def do_sms_login(account_id):
|
||||
"""
|
||||
短信验证码登录(需要人机交互)
|
||||
会弹出浏览器窗口,需要人拖滑块 + 输入验证码。
|
||||
"""
|
||||
db = get_db()
|
||||
account = db.execute('SELECT * FROM accounts WHERE id = ?', (account_id,)).fetchone()
|
||||
db.close()
|
||||
|
||||
if not account:
|
||||
return jsonify(success=False, msg='账号不存在'), 404
|
||||
|
||||
phone = account['phone']
|
||||
if not phone:
|
||||
return jsonify(success=False, msg='该账号未设置手机号'), 400
|
||||
|
||||
# 标记为等待交互
|
||||
db = get_db()
|
||||
db.execute(
|
||||
"UPDATE accounts SET is_logged_in = 0, login_msg = '等待人机交互...', "
|
||||
"updated_at = datetime('now','localtime') WHERE id = ?",
|
||||
(account_id,)
|
||||
)
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
# 后台启动短信登录(会弹出浏览器窗口)
|
||||
_start_bg_sms_login(account_id, phone)
|
||||
|
||||
return jsonify(success=True, msg='已启动短信登录,请在弹出的浏览器中完成滑块验证,并在终端输入验证码')
|
||||
|
||||
|
||||
@bp.route('/status/<int:account_id>')
|
||||
def get_status(account_id):
|
||||
"""轮询账号登录状态"""
|
||||
@@ -100,13 +134,12 @@ def get_status(account_id):
|
||||
|
||||
msg = account['login_msg'] or ''
|
||||
is_logged_in = bool(account['is_logged_in'])
|
||||
# 登录中... 表示还在进行
|
||||
done = msg != '登录中...'
|
||||
done = msg not in ('登录中...', '等待人机交互...')
|
||||
return jsonify(is_logged_in=is_logged_in, login_msg=msg, done=done)
|
||||
|
||||
|
||||
def _start_bg_login(account_id, phone, password):
|
||||
"""在后台线程中执行登录"""
|
||||
"""后台线程执行密码登录"""
|
||||
def _run():
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
@@ -137,3 +170,37 @@ def _start_bg_login(account_id, phone, password):
|
||||
|
||||
t = threading.Thread(target=_run, daemon=True)
|
||||
t.start()
|
||||
|
||||
|
||||
def _start_bg_sms_login(account_id, phone):
|
||||
"""后台线程执行短信登录"""
|
||||
def _run():
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
ok, msg = loop.run_until_complete(
|
||||
login_with_sms(phone, account_id=account_id)
|
||||
)
|
||||
except Exception as e:
|
||||
ok, msg = False, str(e)
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
db = get_db()
|
||||
if ok:
|
||||
db.execute(
|
||||
"UPDATE accounts SET is_logged_in = 1, login_msg = '登录成功', "
|
||||
"updated_at = datetime('now','localtime') WHERE id = ?",
|
||||
(account_id,)
|
||||
)
|
||||
else:
|
||||
db.execute(
|
||||
"UPDATE accounts SET is_logged_in = 0, login_msg = ?, "
|
||||
"updated_at = datetime('now','localtime') WHERE id = ?",
|
||||
(msg, account_id)
|
||||
)
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
t = threading.Thread(target=_run, daemon=True)
|
||||
t.start()
|
||||
|
||||
Reference in New Issue
Block a user