Initial commit: Telegram Management System
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:
你的用户名
2025-11-04 15:37:50 +08:00
commit 237c7802e5
3674 changed files with 525172 additions and 0 deletions

910
test-batch-check.html Normal file
View 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>