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:
openclaw
2026-03-31 15:18:02 +08:00
parent d8c66c0216
commit def06c6360
4 changed files with 941 additions and 69 deletions

View File

@@ -32,7 +32,10 @@
<td>{{ a.updated_at }}</td>
<td>
<button class="btn btn-outline-primary btn-sm" onclick="doLogin({{ a.id }}, this)">
<i class="bi bi-box-arrow-in-right"></i> 登录
<i class="bi bi-key"></i> 密码登录
</button>
<button class="btn btn-outline-info btn-sm" onclick="doSmsLogin({{ a.id }}, this)">
<i class="bi bi-chat-dots"></i> 短信登录
</button>
<button class="btn btn-outline-danger btn-sm" onclick="deleteAccount({{ a.id }})">
<i class="bi bi-trash"></i>
@@ -104,7 +107,27 @@ function doLogin(id, btn) {
fetch('/accounts/login/' + id, { method: 'POST' })
.then(function(r) { return r.json(); })
.then(function() { pollStatus(id, btn); })
.catch(function() { btn.disabled = false; btn.textContent = '重试'; });
.catch(function() { btn.disabled = false; btn.innerHTML = '<i class="bi bi-key"></i> 密码登录'; });
}
function doSmsLogin(id, btn) {
if (!confirm('短信登录需要您在弹出的浏览器中拖动滑块,并在服务器终端输入验证码。确定继续?')) return;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> 等待交互...';
var badge = btn.closest('tr').querySelector('.badge');
if (badge) { badge.className = 'badge bg-info'; badge.textContent = '等待人机交互...'; }
fetch('/accounts/login_sms/' + id, { method: 'POST' })
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.success) {
alert(d.msg);
pollStatus(id, btn);
} else {
alert(d.msg);
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-chat-dots"></i> 短信登录';
}
})
.catch(function() { btn.disabled = false; btn.innerHTML = '<i class="bi bi-chat-dots"></i> 短信登录'; });
}
function pollStatus(id, btn) {
var interval = setInterval(function() {
@@ -127,7 +150,9 @@ function pollStatus(id, btn) {
}
if (btn) {
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-box-arrow-in-right"></i> 登录';
btn.innerHTML = btn.textContent.includes('短信') ?
'<i class="bi bi-chat-dots"></i> 短信登录' :
'<i class="bi bi-key"></i> 密码登录';
}
}
});