mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-07-09 22:41:21 +08:00
feat(registration): 新增Outlook批量注册功能
- 前端界面添加Outlook批量注册选项和账户选择面板 - 后端API新增/registration/outlook-accounts和/registration/outlook-batch端点 - 支持批量选择Outlook账户、自动跳过已注册邮箱、随机间隔控制 - 更新requirements.txt依赖版本
This commit is contained in:
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
@@ -112,6 +112,44 @@ class TaskListResponse(BaseModel):
|
|||||||
tasks: List[RegistrationTaskResponse]
|
tasks: List[RegistrationTaskResponse]
|
||||||
|
|
||||||
|
|
||||||
|
# ============== Outlook 批量注册模型 ==============
|
||||||
|
|
||||||
|
class OutlookAccountForRegistration(BaseModel):
|
||||||
|
"""可用于注册的 Outlook 账户"""
|
||||||
|
id: int # EmailService 表的 ID
|
||||||
|
email: str
|
||||||
|
name: str
|
||||||
|
has_oauth: bool # 是否有 OAuth 配置
|
||||||
|
is_registered: bool # 是否已注册
|
||||||
|
registered_account_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
class OutlookAccountsListResponse(BaseModel):
|
||||||
|
"""Outlook 账户列表响应"""
|
||||||
|
total: int
|
||||||
|
registered_count: int # 已注册数量
|
||||||
|
unregistered_count: int # 未注册数量
|
||||||
|
accounts: List[OutlookAccountForRegistration]
|
||||||
|
|
||||||
|
|
||||||
|
class OutlookBatchRegistrationRequest(BaseModel):
|
||||||
|
"""Outlook 批量注册请求"""
|
||||||
|
service_ids: List[int] # 选中的 EmailService ID
|
||||||
|
skip_registered: bool = True # 自动跳过已注册邮箱
|
||||||
|
proxy: Optional[str] = None
|
||||||
|
interval_min: int = 5
|
||||||
|
interval_max: int = 30
|
||||||
|
|
||||||
|
|
||||||
|
class OutlookBatchRegistrationResponse(BaseModel):
|
||||||
|
"""Outlook 批量注册响应"""
|
||||||
|
batch_id: str
|
||||||
|
total: int # 总数
|
||||||
|
skipped: int # 跳过数(已注册)
|
||||||
|
to_register: int # 待注册数
|
||||||
|
service_ids: List[int] # 实际要注册的服务 ID
|
||||||
|
|
||||||
|
|
||||||
# ============== Helper Functions ==============
|
# ============== Helper Functions ==============
|
||||||
|
|
||||||
def task_to_response(task: RegistrationTask) -> RegistrationTaskResponse:
|
def task_to_response(task: RegistrationTask) -> RegistrationTaskResponse:
|
||||||
@@ -688,3 +726,285 @@ async def get_available_email_services():
|
|||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# ============== Outlook 批量注册 API ==============
|
||||||
|
|
||||||
|
@router.get("/outlook-accounts", response_model=OutlookAccountsListResponse)
|
||||||
|
async def get_outlook_accounts_for_registration():
|
||||||
|
"""
|
||||||
|
获取可用于注册的 Outlook 账户列表
|
||||||
|
|
||||||
|
返回所有已启用的 Outlook 服务,并检查每个邮箱是否已在 accounts 表中注册
|
||||||
|
"""
|
||||||
|
from ...database.models import EmailService as EmailServiceModel
|
||||||
|
from ...database.models import Account
|
||||||
|
|
||||||
|
with get_db() as db:
|
||||||
|
# 获取所有启用的 Outlook 服务
|
||||||
|
outlook_services = db.query(EmailServiceModel).filter(
|
||||||
|
EmailServiceModel.service_type == "outlook",
|
||||||
|
EmailServiceModel.enabled == True
|
||||||
|
).order_by(EmailServiceModel.priority.asc()).all()
|
||||||
|
|
||||||
|
accounts = []
|
||||||
|
registered_count = 0
|
||||||
|
unregistered_count = 0
|
||||||
|
|
||||||
|
for service in outlook_services:
|
||||||
|
config = service.config or {}
|
||||||
|
email = config.get("email") or service.name
|
||||||
|
|
||||||
|
# 检查是否已注册(查询 accounts 表)
|
||||||
|
existing_account = db.query(Account).filter(
|
||||||
|
Account.email == email
|
||||||
|
).first()
|
||||||
|
|
||||||
|
is_registered = existing_account is not None
|
||||||
|
if is_registered:
|
||||||
|
registered_count += 1
|
||||||
|
else:
|
||||||
|
unregistered_count += 1
|
||||||
|
|
||||||
|
accounts.append(OutlookAccountForRegistration(
|
||||||
|
id=service.id,
|
||||||
|
email=email,
|
||||||
|
name=service.name,
|
||||||
|
has_oauth=bool(config.get("client_id") and config.get("refresh_token")),
|
||||||
|
is_registered=is_registered,
|
||||||
|
registered_account_id=existing_account.id if existing_account else None
|
||||||
|
))
|
||||||
|
|
||||||
|
return OutlookAccountsListResponse(
|
||||||
|
total=len(accounts),
|
||||||
|
registered_count=registered_count,
|
||||||
|
unregistered_count=unregistered_count,
|
||||||
|
accounts=accounts
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_outlook_batch_registration(
|
||||||
|
batch_id: str,
|
||||||
|
service_ids: List[int],
|
||||||
|
skip_registered: bool,
|
||||||
|
proxy: Optional[str],
|
||||||
|
interval_min: int,
|
||||||
|
interval_max: int
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
异步执行 Outlook 批量注册任务
|
||||||
|
|
||||||
|
遍历选中的 Outlook 服务,检查邮箱是否已注册,执行注册任务
|
||||||
|
"""
|
||||||
|
from ...database.models import EmailService as EmailServiceModel
|
||||||
|
from ...database.models import Account
|
||||||
|
|
||||||
|
batch_tasks[batch_id] = {
|
||||||
|
"total": len(service_ids),
|
||||||
|
"completed": 0,
|
||||||
|
"success": 0,
|
||||||
|
"failed": 0,
|
||||||
|
"skipped": 0,
|
||||||
|
"cancelled": False,
|
||||||
|
"service_ids": service_ids,
|
||||||
|
"current_index": 0,
|
||||||
|
"logs": []
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
for i, service_id in enumerate(service_ids):
|
||||||
|
# 检查是否已取消
|
||||||
|
if batch_tasks[batch_id]["cancelled"]:
|
||||||
|
logger.info(f"Outlook 批量任务 {batch_id} 已取消")
|
||||||
|
break
|
||||||
|
|
||||||
|
batch_tasks[batch_id]["current_index"] = i
|
||||||
|
|
||||||
|
with get_db() as db:
|
||||||
|
# 获取邮箱服务
|
||||||
|
service = db.query(EmailServiceModel).filter(
|
||||||
|
EmailServiceModel.id == service_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not service:
|
||||||
|
batch_tasks[batch_id]["logs"].append(f"[跳过] 服务 ID {service_id} 不存在")
|
||||||
|
batch_tasks[batch_id]["skipped"] += 1
|
||||||
|
batch_tasks[batch_id]["completed"] += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
config = service.config or {}
|
||||||
|
email = config.get("email") or service.name
|
||||||
|
|
||||||
|
# 检查是否已注册
|
||||||
|
if skip_registered:
|
||||||
|
existing_account = db.query(Account).filter(
|
||||||
|
Account.email == email
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if existing_account:
|
||||||
|
batch_tasks[batch_id]["logs"].append(f"[跳过] {email} 已注册 (账号 ID: {existing_account.id})")
|
||||||
|
batch_tasks[batch_id]["skipped"] += 1
|
||||||
|
batch_tasks[batch_id]["completed"] += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 创建注册任务
|
||||||
|
task_uuid = str(uuid.uuid4())
|
||||||
|
task = crud.create_registration_task(
|
||||||
|
db,
|
||||||
|
task_uuid=task_uuid,
|
||||||
|
proxy=proxy,
|
||||||
|
email_service_id=service_id
|
||||||
|
)
|
||||||
|
|
||||||
|
batch_tasks[batch_id]["logs"].append(f"[注册] 开始注册 {email}...")
|
||||||
|
|
||||||
|
# 运行单个注册任务
|
||||||
|
await run_registration_task(
|
||||||
|
task_uuid, "outlook", proxy, None, service_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# 更新统计
|
||||||
|
with get_db() as db:
|
||||||
|
task = crud.get_registration_task(db, task_uuid)
|
||||||
|
if task:
|
||||||
|
batch_tasks[batch_id]["completed"] += 1
|
||||||
|
if task.status == "completed":
|
||||||
|
batch_tasks[batch_id]["success"] += 1
|
||||||
|
batch_tasks[batch_id]["logs"].append(f"[成功] {email} 注册成功")
|
||||||
|
elif task.status == "failed":
|
||||||
|
batch_tasks[batch_id]["failed"] += 1
|
||||||
|
batch_tasks[batch_id]["logs"].append(f"[失败] {email} 注册失败: {task.error_message}")
|
||||||
|
|
||||||
|
# 如果不是最后一个任务,等待随机间隔
|
||||||
|
if i < len(service_ids) - 1 and not batch_tasks[batch_id]["cancelled"]:
|
||||||
|
wait_time = random.randint(interval_min, interval_max)
|
||||||
|
logger.info(f"Outlook 批量任务 {batch_id}: 等待 {wait_time} 秒后继续下一个任务")
|
||||||
|
await asyncio.sleep(wait_time)
|
||||||
|
|
||||||
|
logger.info(f"Outlook 批量任务 {batch_id} 完成: 成功 {batch_tasks[batch_id]['success']}, 失败 {batch_tasks[batch_id]['failed']}, 跳过 {batch_tasks[batch_id]['skipped']}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Outlook 批量任务 {batch_id} 异常: {e}")
|
||||||
|
batch_tasks[batch_id]["logs"].append(f"[错误] 批量任务异常: {str(e)}")
|
||||||
|
finally:
|
||||||
|
batch_tasks[batch_id]["finished"] = True
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/outlook-batch", response_model=OutlookBatchRegistrationResponse)
|
||||||
|
async def start_outlook_batch_registration(
|
||||||
|
request: OutlookBatchRegistrationRequest,
|
||||||
|
background_tasks: BackgroundTasks
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
启动 Outlook 批量注册任务
|
||||||
|
|
||||||
|
- service_ids: 选中的 EmailService ID 列表
|
||||||
|
- skip_registered: 是否自动跳过已注册邮箱(默认 True)
|
||||||
|
- proxy: 代理地址
|
||||||
|
- interval_min: 最小间隔秒数
|
||||||
|
- interval_max: 最大间隔秒数
|
||||||
|
"""
|
||||||
|
from ...database.models import EmailService as EmailServiceModel
|
||||||
|
from ...database.models import Account
|
||||||
|
|
||||||
|
# 验证参数
|
||||||
|
if not request.service_ids:
|
||||||
|
raise HTTPException(status_code=400, detail="请选择至少一个 Outlook 账户")
|
||||||
|
|
||||||
|
if request.interval_min < 0 or request.interval_max < request.interval_min:
|
||||||
|
raise HTTPException(status_code=400, detail="间隔时间参数无效")
|
||||||
|
|
||||||
|
# 过滤掉已注册的邮箱
|
||||||
|
actual_service_ids = request.service_ids
|
||||||
|
skipped_count = 0
|
||||||
|
|
||||||
|
if request.skip_registered:
|
||||||
|
actual_service_ids = []
|
||||||
|
with get_db() as db:
|
||||||
|
for service_id in request.service_ids:
|
||||||
|
service = db.query(EmailServiceModel).filter(
|
||||||
|
EmailServiceModel.id == service_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not service:
|
||||||
|
continue
|
||||||
|
|
||||||
|
config = service.config or {}
|
||||||
|
email = config.get("email") or service.name
|
||||||
|
|
||||||
|
# 检查是否已注册
|
||||||
|
existing_account = db.query(Account).filter(
|
||||||
|
Account.email == email
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if existing_account:
|
||||||
|
skipped_count += 1
|
||||||
|
else:
|
||||||
|
actual_service_ids.append(service_id)
|
||||||
|
|
||||||
|
if not actual_service_ids:
|
||||||
|
return OutlookBatchRegistrationResponse(
|
||||||
|
batch_id="",
|
||||||
|
total=len(request.service_ids),
|
||||||
|
skipped=skipped_count,
|
||||||
|
to_register=0,
|
||||||
|
service_ids=[]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建批量任务
|
||||||
|
batch_id = str(uuid.uuid4())
|
||||||
|
|
||||||
|
# 初始化批量任务状态
|
||||||
|
batch_tasks[batch_id] = {
|
||||||
|
"total": len(actual_service_ids),
|
||||||
|
"completed": 0,
|
||||||
|
"success": 0,
|
||||||
|
"failed": 0,
|
||||||
|
"skipped": 0,
|
||||||
|
"cancelled": False,
|
||||||
|
"service_ids": actual_service_ids,
|
||||||
|
"current_index": 0,
|
||||||
|
"logs": [],
|
||||||
|
"finished": False
|
||||||
|
}
|
||||||
|
|
||||||
|
# 在后台运行批量注册
|
||||||
|
background_tasks.add_task(
|
||||||
|
run_outlook_batch_registration,
|
||||||
|
batch_id,
|
||||||
|
actual_service_ids,
|
||||||
|
request.skip_registered,
|
||||||
|
request.proxy,
|
||||||
|
request.interval_min,
|
||||||
|
request.interval_max
|
||||||
|
)
|
||||||
|
|
||||||
|
return OutlookBatchRegistrationResponse(
|
||||||
|
batch_id=batch_id,
|
||||||
|
total=len(request.service_ids),
|
||||||
|
skipped=skipped_count,
|
||||||
|
to_register=len(actual_service_ids),
|
||||||
|
service_ids=actual_service_ids
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/outlook-batch/{batch_id}")
|
||||||
|
async def get_outlook_batch_status(batch_id: str):
|
||||||
|
"""获取 Outlook 批量任务状态"""
|
||||||
|
if batch_id not in batch_tasks:
|
||||||
|
raise HTTPException(status_code=404, detail="批量任务不存在")
|
||||||
|
|
||||||
|
batch = batch_tasks[batch_id]
|
||||||
|
return {
|
||||||
|
"batch_id": batch_id,
|
||||||
|
"total": batch["total"],
|
||||||
|
"completed": batch["completed"],
|
||||||
|
"success": batch["success"],
|
||||||
|
"failed": batch["failed"],
|
||||||
|
"skipped": batch.get("skipped", 0),
|
||||||
|
"current_index": batch["current_index"],
|
||||||
|
"cancelled": batch["cancelled"],
|
||||||
|
"finished": batch.get("finished", False),
|
||||||
|
"logs": batch.get("logs", []),
|
||||||
|
"progress": f"{batch['completed']}/{batch['total']}"
|
||||||
|
}
|
||||||
|
|||||||
218
static/js/app.js
218
static/js/app.js
@@ -10,6 +10,8 @@ let logPollingInterval = null;
|
|||||||
let batchPollingInterval = null;
|
let batchPollingInterval = null;
|
||||||
let accountsPollingInterval = null;
|
let accountsPollingInterval = null;
|
||||||
let isBatchMode = false;
|
let isBatchMode = false;
|
||||||
|
let isOutlookBatchMode = false;
|
||||||
|
let outlookAccounts = [];
|
||||||
let availableServices = {
|
let availableServices = {
|
||||||
tempmail: { available: true, services: [] },
|
tempmail: { available: true, services: [] },
|
||||||
outlook: { available: false, services: [] },
|
outlook: { available: false, services: [] },
|
||||||
@@ -21,6 +23,7 @@ const elements = {
|
|||||||
form: document.getElementById('registration-form'),
|
form: document.getElementById('registration-form'),
|
||||||
emailService: document.getElementById('email-service'),
|
emailService: document.getElementById('email-service'),
|
||||||
regMode: document.getElementById('reg-mode'),
|
regMode: document.getElementById('reg-mode'),
|
||||||
|
regModeGroup: document.getElementById('reg-mode-group'),
|
||||||
batchCountGroup: document.getElementById('batch-count-group'),
|
batchCountGroup: document.getElementById('batch-count-group'),
|
||||||
batchCount: document.getElementById('batch-count'),
|
batchCount: document.getElementById('batch-count'),
|
||||||
batchOptions: document.getElementById('batch-options'),
|
batchOptions: document.getElementById('batch-options'),
|
||||||
@@ -47,7 +50,13 @@ const elements = {
|
|||||||
batchRemaining: document.getElementById('batch-remaining'),
|
batchRemaining: document.getElementById('batch-remaining'),
|
||||||
// 已注册账号
|
// 已注册账号
|
||||||
recentAccountsTable: document.getElementById('recent-accounts-table'),
|
recentAccountsTable: document.getElementById('recent-accounts-table'),
|
||||||
refreshAccountsBtn: document.getElementById('refresh-accounts-btn')
|
refreshAccountsBtn: document.getElementById('refresh-accounts-btn'),
|
||||||
|
// Outlook 批量注册
|
||||||
|
outlookBatchSection: document.getElementById('outlook-batch-section'),
|
||||||
|
outlookAccountsContainer: document.getElementById('outlook-accounts-container'),
|
||||||
|
outlookIntervalMin: document.getElementById('outlook-interval-min'),
|
||||||
|
outlookIntervalMax: document.getElementById('outlook-interval-max'),
|
||||||
|
outlookSkipRegistered: document.getElementById('outlook-skip-registered')
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
@@ -136,6 +145,13 @@ function updateEmailServiceOptions() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
select.appendChild(optgroup);
|
select.appendChild(optgroup);
|
||||||
|
|
||||||
|
// Outlook 批量注册选项
|
||||||
|
const batchOption = document.createElement('option');
|
||||||
|
batchOption.value = 'outlook_batch:all';
|
||||||
|
batchOption.textContent = `📋 Outlook 批量注册 (${availableServices.outlook.count} 个账户)`;
|
||||||
|
batchOption.dataset.type = 'outlook_batch';
|
||||||
|
optgroup.appendChild(batchOption);
|
||||||
} else {
|
} else {
|
||||||
const optgroup = document.createElement('optgroup');
|
const optgroup = document.createElement('optgroup');
|
||||||
optgroup.label = '📧 Outlook (未配置)';
|
optgroup.label = '📧 Outlook (未配置)';
|
||||||
@@ -188,6 +204,22 @@ function handleServiceChange(e) {
|
|||||||
const [type, id] = value.split(':');
|
const [type, id] = value.split(':');
|
||||||
const selectedOption = e.target.options[e.target.selectedIndex];
|
const selectedOption = e.target.options[e.target.selectedIndex];
|
||||||
|
|
||||||
|
// 处理 Outlook 批量注册模式
|
||||||
|
if (type === 'outlook_batch') {
|
||||||
|
isOutlookBatchMode = true;
|
||||||
|
elements.outlookBatchSection.style.display = 'block';
|
||||||
|
elements.regModeGroup.style.display = 'none';
|
||||||
|
elements.batchCountGroup.style.display = 'none';
|
||||||
|
elements.batchOptions.style.display = 'none';
|
||||||
|
loadOutlookAccounts();
|
||||||
|
addLog('info', '[系统] 已切换到 Outlook 批量注册模式');
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
isOutlookBatchMode = false;
|
||||||
|
elements.outlookBatchSection.style.display = 'none';
|
||||||
|
elements.regModeGroup.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
// 显示服务信息
|
// 显示服务信息
|
||||||
if (type === 'outlook') {
|
if (type === 'outlook') {
|
||||||
const service = availableServices.outlook.services.find(s => s.id == id);
|
const service = availableServices.outlook.services.find(s => s.id == id);
|
||||||
@@ -221,6 +253,12 @@ async function handleStartRegistration(e) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理 Outlook 批量注册模式
|
||||||
|
if (isOutlookBatchMode) {
|
||||||
|
await handleOutlookBatchRegistration();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const [emailServiceType, serviceId] = selectedValue.split(':');
|
const [emailServiceType, serviceId] = selectedValue.split(':');
|
||||||
|
|
||||||
// 禁用开始按钮
|
// 禁用开始按钮
|
||||||
@@ -595,6 +633,8 @@ function resetButtons() {
|
|||||||
elements.cancelBtn.disabled = true;
|
elements.cancelBtn.disabled = true;
|
||||||
currentTask = null;
|
currentTask = null;
|
||||||
currentBatch = null;
|
currentBatch = null;
|
||||||
|
isBatchMode = false;
|
||||||
|
// 注意:不重置 isOutlookBatchMode,因为用户可能想继续使用 Outlook 批量模式
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTML 转义
|
// HTML 转义
|
||||||
@@ -604,3 +644,179 @@ function escapeHtml(text) {
|
|||||||
div.textContent = text;
|
div.textContent = text;
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============== Outlook 批量注册功能 ==============
|
||||||
|
|
||||||
|
// 加载 Outlook 账户列表
|
||||||
|
async function loadOutlookAccounts() {
|
||||||
|
try {
|
||||||
|
elements.outlookAccountsContainer.innerHTML = '<div class="loading-placeholder" style="text-align: center; padding: var(--spacing-md); color: var(--text-muted);">加载中...</div>';
|
||||||
|
|
||||||
|
const data = await api.get('/registration/outlook-accounts');
|
||||||
|
outlookAccounts = data.accounts || [];
|
||||||
|
|
||||||
|
renderOutlookAccountsList();
|
||||||
|
|
||||||
|
addLog('info', `[系统] 已加载 ${data.total} 个 Outlook 账户 (已注册: ${data.registered_count}, 未注册: ${data.unregistered_count})`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载 Outlook 账户列表失败:', error);
|
||||||
|
elements.outlookAccountsContainer.innerHTML = `<div style="text-align: center; padding: var(--spacing-md); color: var(--text-muted);">加载失败: ${error.message}</div>`;
|
||||||
|
addLog('error', `[错误] 加载 Outlook 账户列表失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染 Outlook 账户列表
|
||||||
|
function renderOutlookAccountsList() {
|
||||||
|
if (outlookAccounts.length === 0) {
|
||||||
|
elements.outlookAccountsContainer.innerHTML = '<div style="text-align: center; padding: var(--spacing-md); color: var(--text-muted);">没有可用的 Outlook 账户</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = outlookAccounts.map(account => `
|
||||||
|
<label class="outlook-account-item" style="display: flex; align-items: center; padding: var(--spacing-sm); border-bottom: 1px solid var(--border-light); cursor: pointer; ${account.is_registered ? 'opacity: 0.6;' : ''}" data-id="${account.id}" data-registered="${account.is_registered}">
|
||||||
|
<input type="checkbox" class="outlook-account-checkbox" value="${account.id}" ${account.is_registered ? '' : 'checked'} style="margin-right: var(--spacing-sm);">
|
||||||
|
<div style="flex: 1;">
|
||||||
|
<div style="font-weight: 500;">${escapeHtml(account.email)}</div>
|
||||||
|
<div style="font-size: 0.75rem; color: var(--text-muted);">
|
||||||
|
${account.is_registered
|
||||||
|
? `<span style="color: var(--success-color);">✓ 已注册</span>`
|
||||||
|
: '<span style="color: var(--primary-color);">未注册</span>'
|
||||||
|
}
|
||||||
|
${account.has_oauth ? ' | OAuth' : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
elements.outlookAccountsContainer.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全选
|
||||||
|
function selectAllOutlookAccounts() {
|
||||||
|
const checkboxes = document.querySelectorAll('.outlook-account-checkbox');
|
||||||
|
checkboxes.forEach(cb => cb.checked = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只选未注册
|
||||||
|
function selectUnregisteredOutlook() {
|
||||||
|
const items = document.querySelectorAll('.outlook-account-item');
|
||||||
|
items.forEach(item => {
|
||||||
|
const checkbox = item.querySelector('.outlook-account-checkbox');
|
||||||
|
const isRegistered = item.dataset.registered === 'true';
|
||||||
|
checkbox.checked = !isRegistered;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消全选
|
||||||
|
function deselectAllOutlookAccounts() {
|
||||||
|
const checkboxes = document.querySelectorAll('.outlook-account-checkbox');
|
||||||
|
checkboxes.forEach(cb => cb.checked = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 Outlook 批量注册
|
||||||
|
async function handleOutlookBatchRegistration() {
|
||||||
|
// 获取选中的账户
|
||||||
|
const selectedIds = [];
|
||||||
|
document.querySelectorAll('.outlook-account-checkbox:checked').forEach(cb => {
|
||||||
|
selectedIds.push(parseInt(cb.value));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedIds.length === 0) {
|
||||||
|
toast.error('请选择至少一个 Outlook 账户');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const intervalMin = parseInt(elements.outlookIntervalMin.value) || 5;
|
||||||
|
const intervalMax = parseInt(elements.outlookIntervalMax.value) || 30;
|
||||||
|
const skipRegistered = elements.outlookSkipRegistered.checked;
|
||||||
|
|
||||||
|
// 禁用开始按钮
|
||||||
|
elements.startBtn.disabled = true;
|
||||||
|
elements.cancelBtn.disabled = false;
|
||||||
|
|
||||||
|
// 清空日志
|
||||||
|
elements.consoleLog.innerHTML = '';
|
||||||
|
|
||||||
|
const requestData = {
|
||||||
|
service_ids: selectedIds,
|
||||||
|
skip_registered: skipRegistered,
|
||||||
|
interval_min: intervalMin,
|
||||||
|
interval_max: intervalMax
|
||||||
|
};
|
||||||
|
|
||||||
|
addLog('info', `[系统] 正在启动 Outlook 批量注册 (${selectedIds.length} 个账户)...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await api.post('/registration/outlook-batch', requestData);
|
||||||
|
|
||||||
|
if (data.to_register === 0) {
|
||||||
|
addLog('warning', '[警告] 所有选中的邮箱都已注册,无需重复注册');
|
||||||
|
toast.warning('所有选中的邮箱都已注册');
|
||||||
|
resetButtons();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBatch = { batch_id: data.batch_id, ...data };
|
||||||
|
addLog('info', `[系统] 批量任务已创建: ${data.batch_id}`);
|
||||||
|
addLog('info', `[系统] 总数: ${data.total}, 跳过已注册: ${data.skipped}, 待注册: ${data.to_register}`);
|
||||||
|
|
||||||
|
// 初始化批量状态显示
|
||||||
|
showBatchStatus({ count: data.to_register });
|
||||||
|
|
||||||
|
// 开始轮询批量状态
|
||||||
|
startOutlookBatchPolling(data.batch_id);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
addLog('error', `[错误] 启动失败: ${error.message}`);
|
||||||
|
toast.error(error.message);
|
||||||
|
resetButtons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始轮询 Outlook 批量状态
|
||||||
|
function startOutlookBatchPolling(batchId) {
|
||||||
|
batchPollingInterval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const data = await api.get(`/registration/outlook-batch/${batchId}`);
|
||||||
|
|
||||||
|
// 更新进度
|
||||||
|
updateBatchProgress({
|
||||||
|
total: data.total,
|
||||||
|
completed: data.completed,
|
||||||
|
success: data.success,
|
||||||
|
failed: data.failed
|
||||||
|
});
|
||||||
|
|
||||||
|
// 输出日志
|
||||||
|
if (data.logs && data.logs.length > 0) {
|
||||||
|
const lastLogIndex = batchPollingInterval.lastLogIndex || 0;
|
||||||
|
for (let i = lastLogIndex; i < data.logs.length; i++) {
|
||||||
|
const log = data.logs[i];
|
||||||
|
const logType = getLogType(log);
|
||||||
|
addLog(logType, log);
|
||||||
|
}
|
||||||
|
batchPollingInterval.lastLogIndex = data.logs.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否完成
|
||||||
|
if (data.finished) {
|
||||||
|
stopBatchPolling();
|
||||||
|
resetButtons();
|
||||||
|
|
||||||
|
addLog('info', `[完成] Outlook 批量任务完成!成功: ${data.success}, 失败: ${data.failed}, 跳过: ${data.skipped || 0}`);
|
||||||
|
if (data.success > 0) {
|
||||||
|
toast.success(`Outlook 批量注册完成,成功 ${data.success} 个`);
|
||||||
|
loadRecentAccounts();
|
||||||
|
} else {
|
||||||
|
toast.warning('Outlook 批量注册完成,但没有成功注册任何账号');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('轮询 Outlook 批量状态失败:', error);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
batchPollingInterval.lastLogIndex = 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -130,10 +130,42 @@
|
|||||||
<option value="tempmail">Tempmail.lol (临时邮箱)</option>
|
<option value="tempmail">Tempmail.lol (临时邮箱)</option>
|
||||||
<option value="outlook">Outlook</option>
|
<option value="outlook">Outlook</option>
|
||||||
<option value="custom_domain">自定义域名</option>
|
<option value="custom_domain">自定义域名</option>
|
||||||
|
<option value="outlook_batch">Outlook 批量注册</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<!-- Outlook 批量注册区域 -->
|
||||||
|
<div id="outlook-batch-section" style="display: none;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>选择账户</label>
|
||||||
|
<div id="outlook-accounts-container" style="max-height: 200px; overflow-y: auto; border: 1px solid var(--border-light); border-radius: var(--radius); padding: var(--spacing-sm);">
|
||||||
|
<div class="loading-placeholder" style="text-align: center; padding: var(--spacing-md); color: var(--text-muted);">
|
||||||
|
加载中...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: var(--spacing-sm); display: flex; gap: var(--spacing-xs); flex-wrap: wrap;">
|
||||||
|
<button type="button" class="btn btn-ghost btn-sm" onclick="selectAllOutlookAccounts()">全选</button>
|
||||||
|
<button type="button" class="btn btn-ghost btn-sm" onclick="selectUnregisteredOutlook()">只选未注册</button>
|
||||||
|
<button type="button" class="btn btn-ghost btn-sm" onclick="deselectAllOutlookAccounts()">取消全选</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="outlook-interval-min">最小间隔 (秒)</label>
|
||||||
|
<input type="number" id="outlook-interval-min" name="outlook_interval_min" min="0" max="300" value="5">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="outlook-interval-max">最大间隔 (秒)</label>
|
||||||
|
<input type="number" id="outlook-interval-max" name="outlook_interval_max" min="1" max="600" value="30">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label style="display: flex; align-items: center; gap: var(--spacing-sm); cursor: pointer;">
|
||||||
|
<input type="checkbox" id="outlook-skip-registered" checked>
|
||||||
|
<span>自动跳过已注册的邮箱</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" id="reg-mode-group">
|
||||||
<label for="reg-mode">注册模式</label>
|
<label for="reg-mode">注册模式</label>
|
||||||
<select id="reg-mode" name="reg_mode">
|
<select id="reg-mode" name="reg_mode">
|
||||||
<option value="single">单次注册</option>
|
<option value="single">单次注册</option>
|
||||||
|
|||||||
Reference in New Issue
Block a user