Initial commit: Telegram Management System
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
Full-stack web application for Telegram management - Frontend: Vue 3 + Vben Admin - Backend: NestJS - Features: User management, group broadcast, statistics 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
910
test-batch-check.html
Normal file
910
test-batch-check.html
Normal file
@@ -0,0 +1,910 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>批量检查账号功能测试</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.section {
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
margin: 5px;
|
||||
background: #2d8cf0;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
button:hover {
|
||||
background: #2b85e4;
|
||||
}
|
||||
button:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.progress {
|
||||
margin: 20px 0;
|
||||
}
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: #19be6b;
|
||||
transition: width 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
/* 检查模式选择 */
|
||||
.check-modes {
|
||||
margin: 20px 0;
|
||||
}
|
||||
.mode-options {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
.mode-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
border: 2px solid #e8eaec;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.mode-option:hover {
|
||||
border-color: #2d8cf0;
|
||||
background: #f0f8ff;
|
||||
}
|
||||
.mode-option input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
.mode-option input[type="radio"]:checked + .mode-icon + .mode-text {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
.mode-option input[type="radio"]:checked ~ * {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
.mode-option:has(input:checked) {
|
||||
border-color: #2d8cf0;
|
||||
background: #f0f8ff;
|
||||
}
|
||||
.mode-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.mode-text {
|
||||
flex: 1;
|
||||
}
|
||||
.mode-text strong {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.mode-text small {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 高级选项 */
|
||||
.advanced-options {
|
||||
margin: 20px 0;
|
||||
}
|
||||
.toggle-header {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: #2d8cf0;
|
||||
}
|
||||
.advanced-panel {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.option-item {
|
||||
margin: 10px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.option-item input[type="checkbox"] {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.option-item select {
|
||||
padding: 5px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 控制按钮 */
|
||||
.control-buttons {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* 账号统计 */
|
||||
.account-stats {
|
||||
margin: 20px 0;
|
||||
}
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
.stat-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e8eaec;
|
||||
}
|
||||
.stat-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.stat-box.total {
|
||||
background: #e6f7ff;
|
||||
}
|
||||
.stat-box.valid {
|
||||
background: #f6ffed;
|
||||
}
|
||||
.stat-box.banned {
|
||||
background: #fff1f0;
|
||||
}
|
||||
.stat-box.checking {
|
||||
background: #e6f7ff;
|
||||
}
|
||||
.stat-box.unchecked {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.log-container {
|
||||
position: relative;
|
||||
}
|
||||
.log {
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
.log-controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.small-btn {
|
||||
padding: 5px 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.minimize-btn {
|
||||
float: right;
|
||||
padding: 5px 10px;
|
||||
margin: 0;
|
||||
background: #e0e0e0;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
.minimize-btn:hover {
|
||||
background: #d0d0d0;
|
||||
}
|
||||
|
||||
/* 迷你进度条样式 */
|
||||
.mini-progress {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: #2d3142;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1000;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
min-width: 400px;
|
||||
}
|
||||
.mini-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
.mini-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.mini-stats {
|
||||
font-size: 12px;
|
||||
color: #a0a0a0;
|
||||
}
|
||||
.mini-bar {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: #1a1a1a;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.mini-fill {
|
||||
height: 100%;
|
||||
background: #2d8cf0;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.mini-expand {
|
||||
background: #2d8cf0;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
.mini-expand:hover {
|
||||
background: #2b85e4;
|
||||
}
|
||||
.log-entry {
|
||||
margin: 3px 0;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.log-entry.info {
|
||||
color: #58a6ff;
|
||||
}
|
||||
.log-entry.success {
|
||||
color: #56d364;
|
||||
font-weight: 500;
|
||||
}
|
||||
.log-entry.error {
|
||||
color: #f85149;
|
||||
font-weight: 500;
|
||||
}
|
||||
.log-entry.warning {
|
||||
color: #d29922;
|
||||
}
|
||||
.log-entry .timestamp {
|
||||
color: #8b949e;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.log-entry .account {
|
||||
color: #ffa657;
|
||||
font-weight: 500;
|
||||
}
|
||||
.current-account {
|
||||
background: #e6f7ff;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.current-account strong {
|
||||
color: #1890ff;
|
||||
}
|
||||
.current-account small {
|
||||
color: #666;
|
||||
}
|
||||
.error {
|
||||
color: #f5222d;
|
||||
background: #fff1f0;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.success {
|
||||
color: #52c41a;
|
||||
background: #f6ffed;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>批量检查账号功能测试</h1>
|
||||
|
||||
<!-- 迷你进度条(最小化时显示) -->
|
||||
<div id="miniProgress" class="mini-progress" style="display: none;" onclick="maximizeWindow()">
|
||||
<div class="mini-content">
|
||||
<span class="mini-title">批量检查进行中...</span>
|
||||
<span class="mini-stats">
|
||||
<span id="miniChecked">0</span>/<span id="miniTotal">0</span>
|
||||
(<span id="miniPercent">0</span>%)
|
||||
</span>
|
||||
<div class="mini-bar">
|
||||
<div id="miniProgressBar" class="mini-fill" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="mini-expand" title="展开">▲</button>
|
||||
</div>
|
||||
|
||||
<!-- 主界面 -->
|
||||
<div id="mainContainer">
|
||||
<div class="section">
|
||||
<h2>操作说明</h2>
|
||||
<p>1. 点击"开始批量检查"按钮启动检查任务</p>
|
||||
<p>2. 系统会实时显示检查进度和统计信息</p>
|
||||
<p>3. 检查完成后会显示总结信息</p>
|
||||
<p>4. 点击"最小化"按钮可以将窗口收起到顶部,继续后台运行</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>批量检查控制
|
||||
<button class="minimize-btn" onclick="minimizeWindow()" title="最小化">▼</button>
|
||||
</h2>
|
||||
|
||||
<!-- 检查模式选择 -->
|
||||
<div class="check-modes">
|
||||
<h3>检查模式</h3>
|
||||
<div class="mode-options">
|
||||
<label class="mode-option">
|
||||
<input type="radio" name="checkMode" value="unBanned" checked>
|
||||
<span class="mode-icon">🟢</span>
|
||||
<span class="mode-text">
|
||||
<strong>未封号账号</strong>
|
||||
<small>只检查当前未封号的账号</small>
|
||||
</span>
|
||||
</label>
|
||||
<label class="mode-option">
|
||||
<input type="radio" name="checkMode" value="all">
|
||||
<span class="mode-icon">🔄</span>
|
||||
<span class="mode-text">
|
||||
<strong>全部账号</strong>
|
||||
<small>检查所有账号(包括已封号)</small>
|
||||
</span>
|
||||
</label>
|
||||
<label class="mode-option">
|
||||
<input type="radio" name="checkMode" value="banned">
|
||||
<span class="mode-icon">🔴</span>
|
||||
<span class="mode-text">
|
||||
<strong>已封号验证</strong>
|
||||
<small>验证已封号账号是否解封</small>
|
||||
</span>
|
||||
</label>
|
||||
<label class="mode-option">
|
||||
<input type="radio" name="checkMode" value="smart">
|
||||
<span class="mode-icon">🧠</span>
|
||||
<span class="mode-text">
|
||||
<strong>智能检查</strong>
|
||||
<small>优先检查高风险账号</small>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 高级选项 -->
|
||||
<div class="advanced-options">
|
||||
<h3 onclick="toggleAdvanced()" class="toggle-header">
|
||||
高级选项 <span id="advancedToggle">▼</span>
|
||||
</h3>
|
||||
<div id="advancedPanel" class="advanced-panel" style="display: none;">
|
||||
<label class="option-item">
|
||||
<input type="checkbox" id="skipRecentChecked">
|
||||
跳过最近检查的账号
|
||||
</label>
|
||||
<div class="option-item">
|
||||
<label>检查间隔:</label>
|
||||
<select id="checkInterval">
|
||||
<option value="30">30分钟</option>
|
||||
<option value="60" selected>1小时</option>
|
||||
<option value="120">2小时</option>
|
||||
<option value="1440">24小时</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 控制按钮 -->
|
||||
<div class="control-buttons">
|
||||
<button id="startBtn" onclick="startBatchCheck()"> 开始批量检查</button>
|
||||
<button id="stopBtn" onclick="stopCheck()" disabled>停止检查</button>
|
||||
<button onclick="loadAccountStats()">刷新统计</button>
|
||||
</div>
|
||||
|
||||
<div id="status"></div>
|
||||
|
||||
<div class="progress" style="display: none;">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressBar" style="width: 0%">0%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 账号统计信息 -->
|
||||
<div class="account-stats">
|
||||
<h3>账号状态统计</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-box total">
|
||||
<div class="stat-icon">📈</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">总账号数</div>
|
||||
<div class="stat-value" id="totalCount">0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-box valid">
|
||||
<div class="stat-icon">🟢</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">正常账号</div>
|
||||
<div class="stat-value" id="validCount">0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-box banned">
|
||||
<div class="stat-icon">🔴</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">封号账号</div>
|
||||
<div class="stat-value" id="bannedCount">0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-box checking">
|
||||
<div class="stat-icon">🔵</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">正在检查</div>
|
||||
<div class="stat-value" id="checkingCount">0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-box unchecked">
|
||||
<div class="stat-icon">⚪</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">未检查</div>
|
||||
<div class="stat-value" id="uncheckedCount">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress" style="display: none;">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressBar" style="width: 0%">0%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="currentAccount" class="current-account" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>实时日志</h2>
|
||||
<div class="log-container">
|
||||
<div class="log" id="log"></div>
|
||||
<div class="log-controls">
|
||||
<button onclick="clearLog()" class="small-btn">清空日志</button>
|
||||
<label>
|
||||
<input type="checkbox" id="autoScroll" checked> 自动滚动
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
|
||||
<script>
|
||||
let socket = null;
|
||||
let taskId = null;
|
||||
let checking = false;
|
||||
let startTime = null;
|
||||
let timerInterval = null;
|
||||
let isMinimized = false;
|
||||
let accountStats = null;
|
||||
|
||||
// 添加日志
|
||||
function addLog(message, type = 'info') {
|
||||
const log = document.getElementById('log');
|
||||
const time = new Date().toLocaleTimeString();
|
||||
const entry = document.createElement('div');
|
||||
entry.className = `log-entry ${type}`;
|
||||
|
||||
// 创建时间戳元素
|
||||
const timestamp = document.createElement('span');
|
||||
timestamp.className = 'timestamp';
|
||||
timestamp.textContent = `[${time}]`;
|
||||
|
||||
// 处理消息,高亮账号
|
||||
const messageSpan = document.createElement('span');
|
||||
const processedMessage = message.replace(/(\+?\d{10,})/g, '<span class="account">$1</span>');
|
||||
messageSpan.innerHTML = processedMessage;
|
||||
|
||||
entry.appendChild(timestamp);
|
||||
entry.appendChild(messageSpan);
|
||||
log.appendChild(entry);
|
||||
|
||||
// 自动滚动
|
||||
if (document.getElementById('autoScroll').checked) {
|
||||
log.scrollTop = log.scrollHeight;
|
||||
}
|
||||
|
||||
// 限制日志条数,防止内存溢出
|
||||
const maxLogs = 1000;
|
||||
while (log.children.length > maxLogs) {
|
||||
log.removeChild(log.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
// 清空日志
|
||||
function clearLog() {
|
||||
const log = document.getElementById('log');
|
||||
log.innerHTML = '';
|
||||
addLog('日志已清空', 'info');
|
||||
}
|
||||
|
||||
// 切换高级选项
|
||||
function toggleAdvanced() {
|
||||
const panel = document.getElementById('advancedPanel');
|
||||
const toggle = document.getElementById('advancedToggle');
|
||||
if (panel.style.display === 'none') {
|
||||
panel.style.display = 'block';
|
||||
toggle.textContent = '▲';
|
||||
} else {
|
||||
panel.style.display = 'none';
|
||||
toggle.textContent = '▼';
|
||||
}
|
||||
}
|
||||
|
||||
// 加载账号统计信息
|
||||
async function loadAccountStats() {
|
||||
try {
|
||||
addLog('正在加载账号统计信息...', 'info');
|
||||
const response = await fetch('http://localhost:3000/accountCheck/stats');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
accountStats = result.data;
|
||||
updateAccountStats(accountStats);
|
||||
addLog('账号统计信息加载完成', 'success');
|
||||
} else {
|
||||
throw new Error(result.msg || '加载失败');
|
||||
}
|
||||
} catch (error) {
|
||||
addLog('加载账号统计失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新账号统计显示
|
||||
function updateAccountStats(stats) {
|
||||
document.getElementById('totalCount').textContent = stats.total || 0;
|
||||
document.getElementById('validCount').textContent = stats.valid || 0;
|
||||
document.getElementById('bannedCount').textContent = stats.banned || 0;
|
||||
document.getElementById('checkingCount').textContent = stats.checking || 0;
|
||||
document.getElementById('uncheckedCount').textContent = stats.unchecked || 0;
|
||||
}
|
||||
|
||||
// 获取检查选项
|
||||
function getCheckOptions() {
|
||||
const mode = document.querySelector('input[name="checkMode"]:checked').value;
|
||||
const skipRecentChecked = document.getElementById('skipRecentChecked').checked;
|
||||
const checkInterval = parseInt(document.getElementById('checkInterval').value);
|
||||
|
||||
return {
|
||||
checkMode: mode,
|
||||
skipRecentChecked,
|
||||
checkInterval
|
||||
};
|
||||
}
|
||||
|
||||
// 更新进度
|
||||
function updateProgress(data) {
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const totalCount = document.getElementById('totalCount');
|
||||
const validCount = document.getElementById('validCount');
|
||||
const bannedCount = document.getElementById('bannedCount');
|
||||
const currentAccount = document.getElementById('currentAccount');
|
||||
|
||||
const percent = data.total > 0 ? Math.round((data.checked / data.total) * 100) : 0;
|
||||
progressBar.style.width = percent + '%';
|
||||
progressBar.textContent = percent + '%';
|
||||
|
||||
totalCount.textContent = data.total || 0;
|
||||
validCount.textContent = data.valid || 0;
|
||||
bannedCount.textContent = data.banned || 0;
|
||||
|
||||
// 更新迷你进度条
|
||||
if (isMinimized) {
|
||||
document.getElementById('miniChecked').textContent = data.checked || 0;
|
||||
document.getElementById('miniTotal').textContent = data.total || 0;
|
||||
document.getElementById('miniPercent').textContent = percent;
|
||||
document.getElementById('miniProgressBar').style.width = percent + '%';
|
||||
}
|
||||
|
||||
if (data.currentAccount) {
|
||||
currentAccount.innerHTML = `<strong>正在检查:</strong> ${data.currentAccount}<br>
|
||||
<small>进度: ${data.checked}/${data.total} (${percent}%)</small>`;
|
||||
currentAccount.style.display = 'block';
|
||||
} else if (data.checking) {
|
||||
// 检查中但没有当前账号,显示等待状态
|
||||
currentAccount.innerHTML = `<strong>当前状态:</strong> 等待下一批次...<br>
|
||||
<small>进度: ${data.checked}/${data.total} (${percent}%)</small>`;
|
||||
currentAccount.style.display = 'block';
|
||||
} else {
|
||||
// 检查完成
|
||||
currentAccount.style.display = 'none';
|
||||
}
|
||||
|
||||
document.querySelector('.progress').style.display = 'block';
|
||||
document.querySelector('.stats').style.display = 'flex';
|
||||
}
|
||||
|
||||
// 初始化Socket连接
|
||||
function initSocket() {
|
||||
addLog('连接到WebSocket服务器...');
|
||||
|
||||
socket = io('http://localhost:3001', {
|
||||
transports: ['websocket', 'polling']
|
||||
});
|
||||
|
||||
socket.on('connect', () => {
|
||||
addLog('WebSocket连接成功', 'success');
|
||||
});
|
||||
|
||||
socket.on('account-check-progress', (data) => {
|
||||
if (data.taskId === taskId) {
|
||||
// 如果有日志消息,直接显示
|
||||
if (data.log) {
|
||||
addLog(data.log, data.logType || 'info');
|
||||
}
|
||||
|
||||
// 更新进度显示
|
||||
updateProgress(data);
|
||||
|
||||
if (!data.checking) {
|
||||
checking = false;
|
||||
document.getElementById('startBtn').disabled = false;
|
||||
document.getElementById('stopBtn').disabled = true;
|
||||
stopTimer();
|
||||
|
||||
if (data.error) {
|
||||
addLog('检查失败: ' + data.error, 'error');
|
||||
document.getElementById('status').innerHTML =
|
||||
'<div class="error">检查失败: ' + data.error + '</div>';
|
||||
} else if (data.endTime) {
|
||||
const duration = Math.round((new Date(data.endTime) - new Date(data.startTime)) / 1000);
|
||||
addLog(`检查完成!总耗时: ${formatDuration(duration)}`, 'success');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('account-check-complete', (data) => {
|
||||
if (data.taskId === taskId) {
|
||||
checking = false;
|
||||
document.getElementById('startBtn').disabled = false;
|
||||
document.getElementById('stopBtn').disabled = true;
|
||||
stopTimer();
|
||||
|
||||
const duration = data.duration || Math.round((Date.now() - startTime) / 1000);
|
||||
addLog(`====== 检查完成 ======`, 'success');
|
||||
addLog(`总计: ${data.total} 个账号`, 'success');
|
||||
addLog(`正常: ${data.valid} (${Math.round(data.valid/data.total*100)}%)`, 'success');
|
||||
addLog(`封号: ${data.banned} (${Math.round(data.banned/data.total*100)}%)`, 'success');
|
||||
if (data.errors) addLog(`错误: ${data.errors} (${Math.round(data.errors/data.total*100)}%)`, 'success');
|
||||
if (data.retries) addLog(`重试次数: ${data.retries}`, 'success');
|
||||
addLog(`总耗时: ${formatDuration(duration)}`, 'success');
|
||||
addLog(`==================`, 'success');
|
||||
|
||||
document.getElementById('status').innerHTML =
|
||||
'<div class="success">检查完成!共检查 ' + data.total + ' 个账号,耗时 ' + formatDuration(duration) + '</div>';
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('account-check-error', (data) => {
|
||||
if (data.taskId === taskId) {
|
||||
checking = false;
|
||||
document.getElementById('startBtn').disabled = false;
|
||||
document.getElementById('stopBtn').disabled = true;
|
||||
|
||||
addLog('检查出错: ' + data.error, 'error');
|
||||
document.getElementById('status').innerHTML =
|
||||
'<div class="error">检查出错: ' + data.error + '</div>';
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
addLog('WebSocket连接断开', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 开始批量检查
|
||||
async function startBatchCheck() {
|
||||
if (checking) return;
|
||||
|
||||
document.getElementById('startBtn').disabled = true;
|
||||
document.getElementById('stopBtn').disabled = false;
|
||||
document.getElementById('status').innerHTML = '';
|
||||
document.getElementById('log').innerHTML = '';
|
||||
|
||||
checking = true;
|
||||
startTime = Date.now();
|
||||
|
||||
if (!socket) {
|
||||
initSocket();
|
||||
}
|
||||
|
||||
addLog('========== 开始批量检查 ==========', 'success');
|
||||
addLog('启动时间: ' + new Date().toLocaleString());
|
||||
addLog('正在启动检查任务...');
|
||||
|
||||
// 启动计时器
|
||||
startTimer();
|
||||
|
||||
try {
|
||||
const options = getCheckOptions();
|
||||
addLog(`检查模式: ${options.checkMode}`, 'info');
|
||||
if (options.skipRecentChecked) {
|
||||
addLog(`跳过最近 ${options.checkInterval} 分钟内检查的账号`, 'info');
|
||||
}
|
||||
|
||||
const response = await fetch('http://localhost:3000/accountCheck/start', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(options)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
taskId = result.data.taskId;
|
||||
addLog('✅ 任务启动成功!', 'success');
|
||||
addLog('任务ID: ' + taskId);
|
||||
addLog('总账号数: ' + result.data.total);
|
||||
addLog('预计耗时: 约' + Math.ceil(result.data.total / 3 * 1.5) + '秒');
|
||||
addLog('--------------------------------');
|
||||
} else {
|
||||
throw new Error(result.msg || '启动失败');
|
||||
}
|
||||
} catch (error) {
|
||||
checking = false;
|
||||
document.getElementById('startBtn').disabled = false;
|
||||
document.getElementById('stopBtn').disabled = true;
|
||||
stopTimer();
|
||||
|
||||
addLog('❌ 启动失败: ' + error.message, 'error');
|
||||
document.getElementById('status').innerHTML =
|
||||
'<div class="error">启动失败: ' + error.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// 停止检查
|
||||
async function stopCheck() {
|
||||
if (!checking || !taskId) return;
|
||||
|
||||
addLog('正在停止检查...');
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:3000/accountCheck/cancel', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ taskId })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
addLog('已发送停止请求', 'success');
|
||||
} catch (error) {
|
||||
addLog('停止失败: ' + error.message, 'error');
|
||||
}
|
||||
|
||||
checking = false;
|
||||
document.getElementById('startBtn').disabled = false;
|
||||
document.getElementById('stopBtn').disabled = true;
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
function formatDuration(seconds) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const secs = seconds % 60;
|
||||
if (minutes > 0) {
|
||||
return `${minutes}分${secs}秒`;
|
||||
}
|
||||
return `${seconds}秒`;
|
||||
}
|
||||
|
||||
// 计时器
|
||||
function startTimer() {
|
||||
timerInterval = setInterval(() => {
|
||||
if (checking && startTime) {
|
||||
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
||||
document.getElementById('status').innerHTML =
|
||||
'<div class="success">正在检查中... 已耗时: ' + formatDuration(elapsed) + '</div>';
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopTimer() {
|
||||
if (timerInterval) {
|
||||
clearInterval(timerInterval);
|
||||
timerInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 最小化窗口
|
||||
function minimizeWindow() {
|
||||
if (!checking) {
|
||||
addLog('只有在检查进行中才能最小化', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
isMinimized = true;
|
||||
document.getElementById('mainContainer').style.display = 'none';
|
||||
document.getElementById('miniProgress').style.display = 'flex';
|
||||
|
||||
// 更新迷你进度条数据
|
||||
const totalCount = document.getElementById('totalCount').textContent;
|
||||
const checkedCount = document.getElementById('validCount').textContent;
|
||||
const percent = document.getElementById('progressBar').style.width;
|
||||
|
||||
document.getElementById('miniTotal').textContent = totalCount;
|
||||
document.getElementById('miniChecked').textContent = checkedCount;
|
||||
document.getElementById('miniPercent').textContent = percent.replace('%', '');
|
||||
document.getElementById('miniProgressBar').style.width = percent;
|
||||
}
|
||||
|
||||
// 最大化窗口
|
||||
function maximizeWindow() {
|
||||
isMinimized = false;
|
||||
document.getElementById('mainContainer').style.display = 'block';
|
||||
document.getElementById('miniProgress').style.display = 'none';
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
window.onload = function() {
|
||||
addLog('🚀 批量检查系统已就绪', 'success');
|
||||
addLog('点击"开始批量检查"按钮启动检查任务');
|
||||
addLog('系统将实时显示检查进度和详细日志');
|
||||
addLog('检查进行中可点击"最小化"按钮收起窗口', 'info');
|
||||
|
||||
// 加载初始账号统计
|
||||
loadAccountStats();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user