mirror of
https://github.com/wojiadexiaoming-copy/eduEmail-cloudflare.git
synced 2026-09-03 06:14:42 +08:00
Add files via upload
可以部署到云端的临时邮箱:workers+unicloud;不需要额外的转发邮箱;你可以部署到你的网站、小程序、app上。
This commit is contained in:
committed by
GitHub
parent
d800cdd503
commit
de17ce7fdc
470
cloudfare-workers后端/workers.js
Normal file
470
cloudfare-workers后端/workers.js
Normal file
@@ -0,0 +1,470 @@
|
||||
export default {
|
||||
async fetch(request, env, ctx) {
|
||||
return new Response('邮件处理Worker运行中', { status: 200 });
|
||||
},
|
||||
|
||||
async email(message, env, ctx) {
|
||||
try {
|
||||
console.log('🚀 开始处理邮件');
|
||||
console.log('📧 发件人:', message.from, '| 收件人:', message.to);
|
||||
|
||||
// 获取原始邮件内容
|
||||
const response = new Response(message.raw);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const rawText = new TextDecoder().decode(arrayBuffer);
|
||||
|
||||
// 显示原始邮件数据
|
||||
console.log('📥 === 接收到的原始邮件数据 ===');
|
||||
console.log('📏 原始邮件大小:', rawText.length, '字符');
|
||||
console.log('📄 原始内容预览:', rawText.substring(0, 500) + '...');
|
||||
console.log('📥 === 原始邮件数据结束 ===');
|
||||
|
||||
// 分离头部和正文
|
||||
const [headers, ...bodyParts] = rawText.split('\r\n\r\n');
|
||||
const body = bodyParts.join('\r\n\r\n');
|
||||
|
||||
// 解析头部
|
||||
const parsedHeaders = this.parseHeaders(headers);
|
||||
|
||||
// 解码主题
|
||||
const subject = this.decodeSubject(parsedHeaders.subject || '');
|
||||
|
||||
// 检查是否为多部分邮件
|
||||
const contentType = parsedHeaders['content-type'] || '';
|
||||
const isMultipart = contentType.includes('multipart');
|
||||
|
||||
console.log('🔍 邮件类型分析:');
|
||||
console.log('📄 Content-Type:', contentType);
|
||||
console.log('🔄 是否多部分邮件:', isMultipart);
|
||||
|
||||
let emailContent = '';
|
||||
let htmlContent = '';
|
||||
|
||||
if (isMultipart) {
|
||||
const result = this.parseMultipartEmail(body, contentType);
|
||||
emailContent = result.text;
|
||||
htmlContent = result.html;
|
||||
|
||||
console.log('📊 多部分解析结果:');
|
||||
console.log('📝 纯文本长度:', emailContent.length, '字符');
|
||||
console.log('🌐 HTML长度:', htmlContent.length, '字符');
|
||||
} else {
|
||||
// 单部分邮件
|
||||
emailContent = this.decodeContent(body, parsedHeaders);
|
||||
console.log('📄 单部分邮件解析完成');
|
||||
}
|
||||
|
||||
// 如果没有纯文本内容,尝试从HTML中提取
|
||||
if (!emailContent && htmlContent) {
|
||||
emailContent = this.extractTextFromHtml(htmlContent);
|
||||
console.log('🔄 从HTML提取纯文本:', emailContent.length, '字符');
|
||||
}
|
||||
|
||||
// 显示解析成功的邮件数据
|
||||
console.log('✅ === 解析成功的邮件数据 ===');
|
||||
console.log('📝 邮件主题:', subject);
|
||||
console.log('📧 发件人:', message.from);
|
||||
console.log('📧 收件人:', message.to);
|
||||
console.log('📄 内容类型:', contentType);
|
||||
console.log('🔄 是否多部分:', isMultipart);
|
||||
console.log('📏 纯文本长度:', emailContent.length, '字符');
|
||||
console.log('🌐 HTML长度:', htmlContent.length, '字符');
|
||||
console.log('📄 内容预览:', emailContent.substring(0, 300) + '...');
|
||||
console.log('✅ === 邮件数据解析结束 ===');
|
||||
|
||||
// 调用UniCloud云函数存储邮件数据
|
||||
console.log('☁️ 步骤4: 调用UniCloud云函数存储邮件数据...');
|
||||
try {
|
||||
await this.callUniCloudFunction(message, subject, emailContent, htmlContent, isMultipart);
|
||||
console.log('✅ UniCloud云函数调用成功');
|
||||
} catch (cloudFunctionError) {
|
||||
console.error('❌ UniCloud云函数调用失败:', cloudFunctionError);
|
||||
// 即使云函数调用失败,也不应该让整个邮件处理失败
|
||||
console.log('⚠️ 尽管云函数失败,邮件处理继续进行');
|
||||
}
|
||||
|
||||
console.log('🎯 邮件处理完成');
|
||||
return new Response('邮件处理成功', { status: 200 });
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 邮件处理错误:', error);
|
||||
console.error('❌ 错误堆栈:', error.stack);
|
||||
return new Response('邮件处理失败', { status: 500 });
|
||||
}
|
||||
},
|
||||
|
||||
// 解析邮件头部
|
||||
parseHeaders(headers) {
|
||||
const parsedHeaders = {};
|
||||
const headerLines = headers.split('\r\n');
|
||||
let currentHeader = '';
|
||||
|
||||
for (const line of headerLines) {
|
||||
if (line.match(/^\s/)) {
|
||||
// 继续上一个头部
|
||||
if (currentHeader) {
|
||||
parsedHeaders[currentHeader] += ' ' + line.trim();
|
||||
}
|
||||
} else {
|
||||
// 新的头部
|
||||
const colonIndex = line.indexOf(':');
|
||||
if (colonIndex > 0) {
|
||||
currentHeader = line.substring(0, colonIndex).toLowerCase();
|
||||
parsedHeaders[currentHeader] = line.substring(colonIndex + 1).trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parsedHeaders;
|
||||
},
|
||||
|
||||
// 解码邮件主题
|
||||
decodeSubject(subject) {
|
||||
if (!subject) return '';
|
||||
|
||||
// 处理 =?charset?encoding?encoded-text?= 格式
|
||||
return subject.replace(/=\?([^?]+)\?([BQ])\?([^?]+)\?=/gi, (match, charset, encoding, encodedText) => {
|
||||
try {
|
||||
if (encoding.toUpperCase() === 'Q') {
|
||||
// Quoted-printable
|
||||
return decodeURIComponent(encodedText.replace(/=/g, '%').replace(/_/g, ' '));
|
||||
} else if (encoding.toUpperCase() === 'B') {
|
||||
// Base64
|
||||
return atob(encodedText);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('主题解码失败:', e);
|
||||
return encodedText;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
},
|
||||
|
||||
// 解析多部分邮件
|
||||
parseMultipartEmail(body, contentType) {
|
||||
const result = { text: '', html: '' };
|
||||
|
||||
try {
|
||||
// 提取boundary
|
||||
const boundaryMatch = contentType.match(/boundary[=:][\s]*["']?([^"'\s;]+)["']?/i);
|
||||
if (!boundaryMatch) {
|
||||
console.warn('未找到boundary');
|
||||
return result;
|
||||
}
|
||||
|
||||
const boundary = boundaryMatch[1];
|
||||
console.log('🔍 找到boundary:', boundary);
|
||||
|
||||
const parts = body.split(`--${boundary}`);
|
||||
console.log('📊 分割出', parts.length, '个部分');
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i].trim();
|
||||
if (!part || part === '--') continue;
|
||||
|
||||
console.log(`🔍 处理第${i}部分:`, part.substring(0, 100) + '...');
|
||||
|
||||
const [partHeaders, ...contentParts] = part.split('\r\n\r\n');
|
||||
if (contentParts.length === 0) continue;
|
||||
|
||||
const partContent = contentParts.join('\r\n\r\n');
|
||||
const partHeadersLower = partHeaders.toLowerCase();
|
||||
|
||||
// 解析部分头部
|
||||
const partHeadersObj = this.parseHeaders(partHeaders);
|
||||
|
||||
if (partHeadersLower.includes('content-type: text/plain')) {
|
||||
result.text = this.decodeContent(partContent, partHeadersObj);
|
||||
console.log('✅ 找到纯文本部分:', result.text.length, '字符');
|
||||
} else if (partHeadersLower.includes('content-type: text/html')) {
|
||||
result.html = this.decodeContent(partContent, partHeadersObj);
|
||||
console.log('✅ 找到HTML部分:', result.html.length, '字符');
|
||||
} else if (partHeadersLower.includes('multipart')) {
|
||||
// 嵌套的多部分,递归处理
|
||||
const nestedResult = this.parseMultipartEmail(partContent, partHeaders);
|
||||
if (nestedResult.text) result.text = nestedResult.text;
|
||||
if (nestedResult.html) result.html = nestedResult.html;
|
||||
console.log('🔄 处理嵌套多部分');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('多部分解析错误:', error);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
// 解码内容
|
||||
decodeContent(content, headers) {
|
||||
let decoded = content;
|
||||
|
||||
const encoding = headers['content-transfer-encoding'] || '';
|
||||
|
||||
if (encoding.toLowerCase().includes('quoted-printable')) {
|
||||
decoded = decoded
|
||||
.replace(/=\r\n/g, '') // 移除软换行
|
||||
.replace(/=([0-9A-F]{2})/gi, (match, hex) => {
|
||||
return String.fromCharCode(parseInt(hex, 16));
|
||||
});
|
||||
} else if (encoding.toLowerCase().includes('base64')) {
|
||||
try {
|
||||
decoded = atob(decoded.replace(/\s/g, ''));
|
||||
} catch (e) {
|
||||
console.warn('Base64解码失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return decoded.trim();
|
||||
},
|
||||
|
||||
// 从HTML中提取纯文本
|
||||
extractTextFromHtml(html) {
|
||||
return html
|
||||
.replace(/<style[^>]*>.*?<\/style>/gis, '') // 移除样式
|
||||
.replace(/<script[^>]*>.*?<\/script>/gis, '') // 移除脚本
|
||||
.replace(/<[^>]+>/g, ' ') // 移除HTML标签
|
||||
.replace(/\s+/g, ' ') // 合并空白字符
|
||||
.trim();
|
||||
},
|
||||
|
||||
// 调用UniCloud云函数存储邮件数据
|
||||
async callUniCloudFunction(message, subject, textContent, htmlContent, isMultipart) {
|
||||
console.log('☁️ ===== 调用UniCloud云函数 =====');
|
||||
|
||||
// 详细记录输入数据状态
|
||||
console.log('📊 输入数据摘要:');
|
||||
console.log(' - 发件人:', message.from);
|
||||
console.log(' - 收件人:', message.to);
|
||||
console.log(' - 邮件主题:', subject);
|
||||
console.log(' - 是否多部分:', isMultipart);
|
||||
console.log(' - 纯文本长度:', textContent.length, '字符');
|
||||
console.log(' - HTML长度:', htmlContent.length, '字符');
|
||||
|
||||
// 验证输入数据的完整性
|
||||
if (!message.from || !message.to) {
|
||||
console.error('❌ 邮件基本信息不完整');
|
||||
throw new Error('邮件基本信息不完整');
|
||||
}
|
||||
|
||||
const cloudFunctionUrl = '云函数链接POST_cloudflare_edukg_email';
|
||||
|
||||
try {
|
||||
// 准备发送给云函数的数据
|
||||
console.log('📦 准备payload数据...');
|
||||
const payload = this.prepareEmailPayload(message, subject, textContent, htmlContent, isMultipart);
|
||||
|
||||
console.log('📦 Payload摘要:');
|
||||
console.log(' - 邮件发件人:', payload.emailInfo.from);
|
||||
console.log(' - 邮件主题:', payload.emailInfo.subject);
|
||||
console.log(' - 邮件类型:', payload.emailInfo.type);
|
||||
console.log(' - 内容长度:', payload.emailInfo.contentLength, '字符');
|
||||
console.log(' - Payload大小:', JSON.stringify(payload).length, '字符');
|
||||
|
||||
// 检查payload大小,避免过大的请求
|
||||
const payloadSize = JSON.stringify(payload).length;
|
||||
if (payloadSize > 10 * 1024 * 1024) { // 10MB限制
|
||||
console.warn('⚠️ Payload大小较大:', Math.round(payloadSize / 1024 / 1024 * 100) / 100, 'MB');
|
||||
}
|
||||
|
||||
console.log('🚀 发送请求到UniCloud云函数...');
|
||||
console.log('🌐 云函数URL:', cloudFunctionUrl);
|
||||
|
||||
// 设置请求超时
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30秒超时
|
||||
|
||||
try {
|
||||
console.log('📡 发起fetch请求...');
|
||||
const response = await fetch(cloudFunctionUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'Cloudflare-Workers-Email-Processor/1.0',
|
||||
'X-Processing-Timestamp': new Date().toISOString(),
|
||||
'X-Email-Type': isMultipart ? 'multipart' : 'simple',
|
||||
'X-Content-Length': textContent.length.toString()
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
signal: controller.signal
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
console.log('📡 响应状态:', response.status, response.statusText);
|
||||
|
||||
// 获取响应头
|
||||
const headers = {};
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key] = value;
|
||||
});
|
||||
console.log('📋 响应头:', headers);
|
||||
|
||||
if (response.ok) {
|
||||
console.log('📄 读取响应内容...');
|
||||
const result = await response.json();
|
||||
console.log('✅ UniCloud云函数执行成功!');
|
||||
console.log('📄 响应数据:', JSON.stringify(result, null, 2));
|
||||
|
||||
// 记录处理结果
|
||||
if (result.success) {
|
||||
console.log('🎉 数据处理完成!');
|
||||
if (result.insertedId) {
|
||||
console.log('💾 数据库记录ID:', result.insertedId);
|
||||
}
|
||||
if (result.processingTime) {
|
||||
console.log('⏱️ 处理时间:', result.processingTime, '毫秒');
|
||||
}
|
||||
if (result.message) {
|
||||
console.log('💬 成功消息:', result.message);
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ 云函数执行但报告错误:', result.error || '未知错误');
|
||||
}
|
||||
} else {
|
||||
console.log('📄 读取错误响应...');
|
||||
const errorText = await response.text();
|
||||
console.error('❌ UniCloud云函数调用失败!');
|
||||
console.error('📋 错误响应:', errorText);
|
||||
|
||||
const errorMessage = this.getDetailedErrorMessage(response.status, errorText);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
} catch (fetchError) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (fetchError.name === 'AbortError') {
|
||||
console.error('⏰ 请求超时(30秒)');
|
||||
throw new Error('请求超时(30秒)');
|
||||
}
|
||||
console.error('📡 Fetch错误:', fetchError);
|
||||
throw fetchError;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 调用UniCloud云函数错误:', error);
|
||||
console.error('📋 错误详情:', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
functionUrl: cloudFunctionUrl,
|
||||
emailSubject: subject
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 准备邮件payload数据
|
||||
prepareEmailPayload(message, subject, textContent, htmlContent, isMultipart) {
|
||||
console.log('📦 开始准备邮件payload...');
|
||||
|
||||
// 安全地处理邮件内容
|
||||
const safeSubject = this.sanitizeString(subject || '无主题');
|
||||
const safeFrom = message.from || '未知发件人';
|
||||
const safeTo = message.to || '未知收件人';
|
||||
|
||||
// 确定邮件类型
|
||||
let emailType = 'text';
|
||||
if (isMultipart && htmlContent && textContent) {
|
||||
emailType = 'multipart';
|
||||
} else if (htmlContent) {
|
||||
emailType = 'html';
|
||||
}
|
||||
|
||||
// 按照云函数期望的格式准备数据
|
||||
const payload = {
|
||||
// 邮件基本信息
|
||||
emailInfo: {
|
||||
from: safeFrom,
|
||||
to: safeTo,
|
||||
subject: safeSubject,
|
||||
date: new Date().toISOString(),
|
||||
messageId: this.generateMessageId(),
|
||||
hasHtml: !!htmlContent,
|
||||
hasText: !!textContent
|
||||
},
|
||||
|
||||
// 邮件内容(云函数期望的格式)
|
||||
emailContent: {
|
||||
html: htmlContent,
|
||||
text: textContent,
|
||||
htmlLength: htmlContent.length,
|
||||
textLength: textContent.length
|
||||
},
|
||||
|
||||
// 附件信息(保持兼容性)
|
||||
attachment: null,
|
||||
|
||||
// DMARC记录(保持兼容性)
|
||||
dmarcRecords: [],
|
||||
|
||||
// 处理信息
|
||||
processedAt: new Date().toISOString(),
|
||||
workerInfo: {
|
||||
version: '1.0.0',
|
||||
source: 'cloudflare-workers-email-parser'
|
||||
}
|
||||
};
|
||||
|
||||
console.log('✅ Payload准备完成(兼容格式)');
|
||||
console.log('📊 内容验证:');
|
||||
console.log(' - 文本内容长度:', textContent.length);
|
||||
console.log(' - HTML内容长度:', htmlContent.length);
|
||||
console.log(' - hasText:', !!textContent);
|
||||
console.log(' - hasHtml:', !!htmlContent);
|
||||
|
||||
return payload;
|
||||
},
|
||||
|
||||
// 生成消息ID
|
||||
generateMessageId() {
|
||||
const timestamp = Date.now();
|
||||
const random = Math.random().toString(36).substring(2, 15);
|
||||
return `${timestamp}-${random}@cloudflare-worker`;
|
||||
},
|
||||
|
||||
// 获取详细错误信息
|
||||
getDetailedErrorMessage(status, errorText) {
|
||||
switch (status) {
|
||||
case 400:
|
||||
return `请求参数错误 (400): ${errorText}`;
|
||||
case 401:
|
||||
return `认证失败 (401): ${errorText}`;
|
||||
case 403:
|
||||
return `权限不足 (403): ${errorText}`;
|
||||
case 404:
|
||||
return `云函数未找到 (404): ${errorText}`;
|
||||
case 500:
|
||||
return `服务器内部错误 (500): ${errorText}`;
|
||||
case 502:
|
||||
return `网关错误 (502): ${errorText}`;
|
||||
case 503:
|
||||
return `服务不可用 (503): ${errorText}`;
|
||||
case 504:
|
||||
return `网关超时 (504): ${errorText}`;
|
||||
default:
|
||||
return `HTTP错误 (${status}): ${errorText}`;
|
||||
}
|
||||
},
|
||||
|
||||
// 字符串清理函数
|
||||
sanitizeString(input) {
|
||||
if (!input) return '未知';
|
||||
|
||||
try {
|
||||
let cleaned = input
|
||||
.replace(/[\u0000-\u001F\u007F-\u009F]/g, '') // 移除控制字符
|
||||
.replace(/[\uFFFD]/g, '?') // 替换替换字符
|
||||
.trim();
|
||||
|
||||
if (!cleaned) return '未知';
|
||||
|
||||
// 限制长度避免日志过长
|
||||
if (cleaned.length > 200) {
|
||||
cleaned = cleaned.substring(0, 200) + '...';
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
} catch (error) {
|
||||
console.warn('⚠️ 字符串清理失败:', error);
|
||||
return '编码错误';
|
||||
}
|
||||
}
|
||||
};
|
||||
345
uniCloud/cloudfunctions/Delete_edu_cloudfare/index.js
Normal file
345
uniCloud/cloudfunctions/Delete_edu_cloudfare/index.js
Normal file
@@ -0,0 +1,345 @@
|
||||
'use strict';
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
// CORS工具函数
|
||||
class CorsUtils {
|
||||
// 获取请求来源
|
||||
static getOrigin(headers) {
|
||||
return headers?.origin || headers?.Origin || '*';
|
||||
}
|
||||
|
||||
// 设置CORS响应头
|
||||
static setCorsHeaders(origin, additionalHeaders = {}) {
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': origin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, Accept, Origin',
|
||||
'Access-Control-Allow-Credentials': 'true',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
...additionalHeaders
|
||||
};
|
||||
}
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
static handleOptionsRequest(headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify({ message: 'OK' })
|
||||
};
|
||||
}
|
||||
|
||||
// 创建成功响应
|
||||
static successResponse(data, headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
}
|
||||
|
||||
// 创建错误响应
|
||||
static errorResponse(error, statusCode = 500, headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message || error
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// 验证HTTP方法
|
||||
static validateMethod(httpMethod, allowedMethods = ['POST']) {
|
||||
return allowedMethods.includes(httpMethod);
|
||||
}
|
||||
|
||||
// 解析请求体
|
||||
static parseBody(body) {
|
||||
try {
|
||||
return typeof body === 'string' ? JSON.parse(body) : body;
|
||||
} catch (error) {
|
||||
throw new Error('无效的请求体格式');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 配置文件
|
||||
const config = {
|
||||
cloudflare: {
|
||||
api_token: "※※※※※※※※※※※※※※※※※※※※※※※※※※※※",
|
||||
zone_id: "※※※※※※※※※※※※※※※※※※※※※※※※※※",
|
||||
domain: "※※※※※※※※※"
|
||||
}
|
||||
};
|
||||
|
||||
// Cloudflare API操作类
|
||||
class CloudflareAPI {
|
||||
constructor() {
|
||||
this.apiToken = config.cloudflare.api_token;
|
||||
this.zoneId = config.cloudflare.zone_id;
|
||||
this.domain = config.cloudflare.domain;
|
||||
this.baseURL = 'https://api.cloudflare.com/client/v4';
|
||||
}
|
||||
|
||||
// 删除邮箱路由
|
||||
async deleteEmailRoutes(email) {
|
||||
console.log('=== 开始删除Cloudflare邮箱路由 ===');
|
||||
console.log('目标邮箱:', email);
|
||||
|
||||
try {
|
||||
// 首先获取所有邮箱路由规则
|
||||
const listResponse = await axios.get(
|
||||
`${this.baseURL}/zones/${this.zoneId}/email/routing/rules`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.apiToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!listResponse.data.success) {
|
||||
throw new Error(`获取邮箱路由列表失败: ${JSON.stringify(listResponse.data.errors)}`);
|
||||
}
|
||||
|
||||
console.log('获取到的路由规则总数:', listResponse.data.result.length);
|
||||
|
||||
// 查找匹配的路由规则
|
||||
const matchingRules = listResponse.data.result.filter(rule => {
|
||||
return rule.matchers && rule.matchers.some(matcher =>
|
||||
matcher.field === 'to' && matcher.value === email
|
||||
);
|
||||
});
|
||||
|
||||
console.log('找到匹配的路由规则数:', matchingRules.length);
|
||||
console.log('匹配的路由规则:', JSON.stringify(matchingRules, null, 2));
|
||||
|
||||
if (matchingRules.length === 0) {
|
||||
console.log('未找到该邮箱的路由规则');
|
||||
return {
|
||||
success: true,
|
||||
message: '未找到该邮箱的路由规则',
|
||||
deletedCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
// 删除所有匹配的路由规则
|
||||
const deletePromises = matchingRules.map(async (rule) => {
|
||||
console.log(`删除路由规则 ID: ${rule.id}`);
|
||||
|
||||
try {
|
||||
const deleteResponse = await axios.delete(
|
||||
`${this.baseURL}/zones/${this.zoneId}/email/routing/rules/${rule.id}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.apiToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!deleteResponse.data.success) {
|
||||
console.error(`删除路由规则 ${rule.id} 失败:`, deleteResponse.data.errors);
|
||||
throw new Error(`删除路由规则失败: ${JSON.stringify(deleteResponse.data.errors)}`);
|
||||
}
|
||||
|
||||
console.log(`✅ 成功删除路由规则 ${rule.id}`);
|
||||
return { success: true, ruleId: rule.id };
|
||||
} catch (error) {
|
||||
console.error(`删除路由规则 ${rule.id} 时出错:`, error);
|
||||
return { success: false, ruleId: rule.id, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
const deleteResults = await Promise.all(deletePromises);
|
||||
const successCount = deleteResults.filter(result => result.success).length;
|
||||
const failedCount = deleteResults.filter(result => !result.success).length;
|
||||
|
||||
console.log(`删除结果: 成功 ${successCount} 个, 失败 ${failedCount} 个`);
|
||||
|
||||
return {
|
||||
success: failedCount === 0,
|
||||
message: `删除了 ${successCount} 个路由规则${failedCount > 0 ? `, ${failedCount} 个失败` : ''}`,
|
||||
deletedCount: successCount,
|
||||
failedCount: failedCount,
|
||||
details: deleteResults
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('=== 删除Cloudflare邮箱路由失败 ===');
|
||||
console.error('错误详情:', error);
|
||||
|
||||
if (error.response) {
|
||||
console.error('错误响应状态码:', error.response.status);
|
||||
console.error('错误响应数据:', JSON.stringify(error.response.data, null, 2));
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
console.log('=== Delete_edu_cloudfare 云函数开始执行 ===');
|
||||
console.log('接收到的事件参数:', JSON.stringify(event, null, 2));
|
||||
|
||||
try {
|
||||
// 解析HTTP请求
|
||||
const { httpMethod, body, headers } = event;
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
if (httpMethod === 'OPTIONS') {
|
||||
console.log('处理OPTIONS预检请求');
|
||||
return CorsUtils.handleOptionsRequest(headers);
|
||||
}
|
||||
|
||||
// 验证HTTP方法
|
||||
if (!CorsUtils.validateMethod(httpMethod, ['POST'])) {
|
||||
console.log('HTTP方法不允许:', httpMethod);
|
||||
return CorsUtils.errorResponse('方法不允许', 405, headers);
|
||||
}
|
||||
|
||||
// 解析请求体
|
||||
let requestData;
|
||||
try {
|
||||
requestData = CorsUtils.parseBody(body) || event;
|
||||
} catch (parseError) {
|
||||
console.error('请求体解析失败:', parseError);
|
||||
return CorsUtils.errorResponse(parseError, 400, headers);
|
||||
}
|
||||
|
||||
const { email } = requestData;
|
||||
|
||||
if (!email) {
|
||||
console.error('缺少必需的参数: email');
|
||||
return CorsUtils.errorResponse('缺少邮箱地址参数', 400, headers);
|
||||
}
|
||||
|
||||
console.log('准备删除邮箱:', email);
|
||||
|
||||
// 获取数据库引用
|
||||
const db = uniCloud.database();
|
||||
|
||||
// 删除结果统计
|
||||
const deleteResults = {
|
||||
cloudflare: { success: false, message: '', deletedCount: 0 },
|
||||
emailData: { success: false, message: '', deletedCount: 0 },
|
||||
tempEmails: { success: false, message: '', deletedCount: 0 }
|
||||
};
|
||||
|
||||
// 1. 删除Cloudflare邮箱路由
|
||||
console.log('=== 步骤1: 删除Cloudflare邮箱路由 ===');
|
||||
try {
|
||||
const cloudflare = new CloudflareAPI();
|
||||
const cloudflareResult = await cloudflare.deleteEmailRoutes(email);
|
||||
deleteResults.cloudflare = {
|
||||
success: cloudflareResult.success,
|
||||
message: cloudflareResult.message,
|
||||
deletedCount: cloudflareResult.deletedCount || 0
|
||||
};
|
||||
console.log('Cloudflare删除结果:', deleteResults.cloudflare);
|
||||
} catch (cloudflareError) {
|
||||
console.error('删除Cloudflare路由失败:', cloudflareError);
|
||||
deleteResults.cloudflare = {
|
||||
success: false,
|
||||
message: `删除Cloudflare路由失败: ${cloudflareError.message}`,
|
||||
deletedCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
// 2. 删除cloudflare_edukg_email集合中的邮件数据
|
||||
console.log('=== 步骤2: 删除邮件数据 ===');
|
||||
try {
|
||||
const emailCollection = db.collection('cloudflare_edukg_email');
|
||||
const emailDeleteResult = await emailCollection
|
||||
.where({
|
||||
emailTo: email
|
||||
})
|
||||
.remove();
|
||||
|
||||
deleteResults.emailData = {
|
||||
success: true,
|
||||
message: `删除了 ${emailDeleteResult.deleted} 条邮件记录`,
|
||||
deletedCount: emailDeleteResult.deleted
|
||||
};
|
||||
console.log('邮件数据删除结果:', deleteResults.emailData);
|
||||
} catch (emailError) {
|
||||
console.error('删除邮件数据失败:', emailError);
|
||||
deleteResults.emailData = {
|
||||
success: false,
|
||||
message: `删除邮件数据失败: ${emailError.message}`,
|
||||
deletedCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
// 3. 删除temp_emails集合中的邮箱记录
|
||||
console.log('=== 步骤3: 删除临时邮箱记录 ===');
|
||||
try {
|
||||
const tempEmailCollection = db.collection('temp_emails');
|
||||
const tempEmailDeleteResult = await tempEmailCollection
|
||||
.where({
|
||||
email: email
|
||||
})
|
||||
.remove();
|
||||
|
||||
deleteResults.tempEmails = {
|
||||
success: true,
|
||||
message: `删除了 ${tempEmailDeleteResult.deleted} 条邮箱记录`,
|
||||
deletedCount: tempEmailDeleteResult.deleted
|
||||
};
|
||||
console.log('临时邮箱记录删除结果:', deleteResults.tempEmails);
|
||||
} catch (tempEmailError) {
|
||||
console.error('删除临时邮箱记录失败:', tempEmailError);
|
||||
deleteResults.tempEmails = {
|
||||
success: false,
|
||||
message: `删除临时邮箱记录失败: ${tempEmailError.message}`,
|
||||
deletedCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
// 汇总删除结果
|
||||
const overallSuccess = deleteResults.cloudflare.success ||
|
||||
deleteResults.emailData.success ||
|
||||
deleteResults.tempEmails.success;
|
||||
|
||||
const totalDeleted = deleteResults.cloudflare.deletedCount +
|
||||
deleteResults.emailData.deletedCount +
|
||||
deleteResults.tempEmails.deletedCount;
|
||||
|
||||
const responseData = {
|
||||
success: overallSuccess,
|
||||
message: overallSuccess ?
|
||||
`邮箱删除完成,共删除 ${totalDeleted} 项数据` :
|
||||
'邮箱删除失败',
|
||||
email: email,
|
||||
details: deleteResults,
|
||||
summary: {
|
||||
totalDeleted: totalDeleted,
|
||||
cloudflareRoutes: deleteResults.cloudflare.deletedCount,
|
||||
emailRecords: deleteResults.emailData.deletedCount,
|
||||
tempEmailRecords: deleteResults.tempEmails.deletedCount
|
||||
}
|
||||
};
|
||||
|
||||
console.log('=== 删除操作完成 ===');
|
||||
console.log('最终结果:', JSON.stringify(responseData, null, 2));
|
||||
|
||||
return CorsUtils.successResponse(responseData, headers);
|
||||
|
||||
} catch (error) {
|
||||
console.error('=== Delete_edu_cloudfare 云函数执行失败 ===');
|
||||
console.error('错误详情:', error);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
|
||||
// 确保headers变量可用
|
||||
const headers = event?.headers || {};
|
||||
return CorsUtils.errorResponse(error, 500, headers);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "Delete_edu_cloudfare",
|
||||
"version": "1.0.0",
|
||||
"description": "删除临时邮箱及相关数据",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"axios": "^1.5.0"
|
||||
}
|
||||
}
|
||||
250
uniCloud/cloudfunctions/GET_all_temp_emails/index.js
Normal file
250
uniCloud/cloudfunctions/GET_all_temp_emails/index.js
Normal file
@@ -0,0 +1,250 @@
|
||||
'use strict';
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
// CORS工具函数
|
||||
class CorsUtils {
|
||||
// 获取请求来源
|
||||
static getOrigin(headers) {
|
||||
return headers?.origin || headers?.Origin || '*';
|
||||
}
|
||||
|
||||
// 设置CORS响应头
|
||||
static setCorsHeaders(origin, additionalHeaders = {}) {
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': origin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, Accept, Origin',
|
||||
'Access-Control-Allow-Credentials': 'true',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
...additionalHeaders
|
||||
};
|
||||
}
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
static handleOptionsRequest(headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify({ message: 'OK' })
|
||||
};
|
||||
}
|
||||
|
||||
// 创建成功响应
|
||||
static successResponse(data, headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
}
|
||||
|
||||
// 创建错误响应
|
||||
static errorResponse(error, statusCode = 500, headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message || error
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Cloudflare 配置
|
||||
const config = {
|
||||
cloudflare: {
|
||||
api_token: "※※※※※※※※※※※※※※※※※※※※※※※※※※※※",
|
||||
zone_id: "※※※※※※※※※※※※※※※※※※※※※※※※※※",
|
||||
domain: "※※※※※※※※※"
|
||||
}
|
||||
};
|
||||
|
||||
// Cloudflare API操作类
|
||||
class CloudflareAPI {
|
||||
constructor() {
|
||||
this.apiToken = config.cloudflare.api_token;
|
||||
this.zoneId = config.cloudflare.zone_id;
|
||||
this.domain = config.cloudflare.domain;
|
||||
this.baseURL = 'https://api.cloudflare.com/client/v4';
|
||||
}
|
||||
|
||||
// 获取所有邮箱路由规则
|
||||
async getAllEmailRoutes() {
|
||||
console.log('🔍 正在获取Cloudflare邮箱路由规则...');
|
||||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${this.baseURL}/zones/${this.zoneId}/email/routing/rules`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.apiToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.data.success) {
|
||||
throw new Error(`获取路由规则失败: ${JSON.stringify(response.data.errors)}`);
|
||||
}
|
||||
|
||||
const rules = response.data.result;
|
||||
console.log(`📋 找到 ${rules.length} 个邮箱路由规则`);
|
||||
|
||||
return rules;
|
||||
} catch (error) {
|
||||
console.error('❌ 获取邮箱路由规则失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤临时邮箱规则
|
||||
filterTempEmailRoutes(rules) {
|
||||
console.log('🔍 正在筛选临时邮箱规则...');
|
||||
|
||||
const tempRules = rules.filter(rule => {
|
||||
// 检查规则名称是否以 "temp-" 开头
|
||||
const isTempByName = rule.name && rule.name.startsWith('temp-');
|
||||
|
||||
// 检查是否匹配我们的域名
|
||||
const isDomainMatch = rule.matchers && rule.matchers.some(matcher =>
|
||||
matcher.field === 'to' &&
|
||||
matcher.value &&
|
||||
matcher.value.includes(this.domain)
|
||||
);
|
||||
|
||||
// 检查是否是 Worker 类型的路由
|
||||
const isWorkerRoute = rule.actions && rule.actions.some(action =>
|
||||
action.type === 'worker'
|
||||
);
|
||||
|
||||
return isTempByName || (isDomainMatch && isWorkerRoute);
|
||||
});
|
||||
|
||||
console.log(`📝 筛选出 ${tempRules.length} 个临时邮箱规则`);
|
||||
|
||||
// 显示详细信息
|
||||
tempRules.forEach((rule, index) => {
|
||||
const email = rule.matchers?.[0]?.value || '未知邮箱';
|
||||
const workerName = rule.actions?.[0]?.value?.[0] || '未知Worker';
|
||||
console.log(` ${index + 1}. ${rule.name} - ${email} -> ${workerName}`);
|
||||
});
|
||||
|
||||
return tempRules;
|
||||
}
|
||||
}
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
console.log('=== GET_all_temp_emails 云函数开始执行 ===');
|
||||
console.log('接收到的事件参数:', JSON.stringify(event, null, 2));
|
||||
|
||||
try {
|
||||
// 解析HTTP请求
|
||||
const { httpMethod, headers } = event;
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
if (httpMethod === 'OPTIONS') {
|
||||
console.log('处理OPTIONS预检请求');
|
||||
return CorsUtils.handleOptionsRequest(headers);
|
||||
}
|
||||
|
||||
// 验证HTTP方法
|
||||
if (httpMethod !== 'POST' && httpMethod !== 'GET') {
|
||||
console.log('HTTP方法不允许:', httpMethod);
|
||||
return CorsUtils.errorResponse('方法不允许', 405, headers);
|
||||
}
|
||||
|
||||
console.log('开始从Cloudflare获取所有临时邮箱...');
|
||||
|
||||
// 创建Cloudflare API实例
|
||||
const cloudflareAPI = new CloudflareAPI();
|
||||
|
||||
// 获取所有邮箱路由规则
|
||||
const allRoutes = await cloudflareAPI.getAllEmailRoutes();
|
||||
|
||||
// 筛选临时邮箱规则
|
||||
const tempRoutes = cloudflareAPI.filterTempEmailRoutes(allRoutes);
|
||||
|
||||
// 转换为邮箱列表格式
|
||||
const emailsWithStats = [];
|
||||
const db = uniCloud.database();
|
||||
|
||||
for (const rule of tempRoutes) {
|
||||
const email = rule.matchers?.[0]?.value || '未知邮箱';
|
||||
|
||||
try {
|
||||
// 查询该邮箱的邮件数量
|
||||
const emailCountResult = await db.collection('cloudflare_edukg_email')
|
||||
.where({
|
||||
emailTo: email
|
||||
})
|
||||
.count();
|
||||
|
||||
emailsWithStats.push({
|
||||
id: rule.id,
|
||||
email: email,
|
||||
ruleName: rule.name,
|
||||
createdAt: rule.created_on,
|
||||
emailCount: emailCountResult.total,
|
||||
workerName: rule.actions?.[0]?.value?.[0] || '未知Worker',
|
||||
enabled: rule.enabled
|
||||
});
|
||||
|
||||
console.log(`邮箱 ${email} 有 ${emailCountResult.total} 封邮件`);
|
||||
} catch (error) {
|
||||
console.error(`查询邮箱 ${email} 的邮件数量失败:`, error);
|
||||
// 即使查询邮件数量失败,也要包含这个邮箱
|
||||
emailsWithStats.push({
|
||||
id: rule.id,
|
||||
email: email,
|
||||
ruleName: rule.name,
|
||||
createdAt: rule.created_on,
|
||||
emailCount: 0,
|
||||
workerName: rule.actions?.[0]?.value?.[0] || '未知Worker',
|
||||
enabled: rule.enabled,
|
||||
error: '查询邮件数量失败'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 计算总统计信息
|
||||
const totalEmails = emailsWithStats.length;
|
||||
const totalEmailMessages = emailsWithStats.reduce((sum, item) => sum + item.emailCount, 0);
|
||||
const activeEmails = emailsWithStats.filter(item => item.enabled).length;
|
||||
|
||||
const responseData = {
|
||||
success: true,
|
||||
message: '成功从Cloudflare获取所有临时邮箱',
|
||||
data: {
|
||||
emails: emailsWithStats,
|
||||
statistics: {
|
||||
totalEmails: totalEmails,
|
||||
activeEmails: activeEmails,
|
||||
disabledEmails: totalEmails - activeEmails,
|
||||
totalEmailMessages: totalEmailMessages
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
console.log('=== 查询完成 ===');
|
||||
console.log('总邮箱数:', totalEmails);
|
||||
console.log('活跃邮箱数:', activeEmails);
|
||||
console.log('总邮件数:', totalEmailMessages);
|
||||
|
||||
return CorsUtils.successResponse(responseData, headers);
|
||||
|
||||
} catch (error) {
|
||||
console.error('=== GET_all_temp_emails 云函数执行失败 ===');
|
||||
console.error('错误详情:', error);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
|
||||
// 确保headers变量可用
|
||||
const headers = event?.headers || {};
|
||||
return CorsUtils.errorResponse(error, 500, headers);
|
||||
}
|
||||
};
|
||||
9
uniCloud/cloudfunctions/GET_all_temp_emails/package.json
Normal file
9
uniCloud/cloudfunctions/GET_all_temp_emails/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "GET_all_temp_emails",
|
||||
"version": "1.0.0",
|
||||
"description": "从Cloudflare获取所有临时邮箱记录",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"axios": "^1.5.0"
|
||||
}
|
||||
}
|
||||
237
uniCloud/cloudfunctions/GET_cloudflare_edukg_email/index.js
Normal file
237
uniCloud/cloudfunctions/GET_cloudflare_edukg_email/index.js
Normal file
@@ -0,0 +1,237 @@
|
||||
'use strict';
|
||||
|
||||
// CORS工具函数
|
||||
class CorsUtils {
|
||||
// 获取请求来源
|
||||
static getOrigin(headers) {
|
||||
return headers?.origin || headers?.Origin || '*';
|
||||
}
|
||||
|
||||
// 设置CORS响应头
|
||||
static setCorsHeaders(origin, additionalHeaders = {}) {
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': origin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, Accept, Origin',
|
||||
'Access-Control-Allow-Credentials': 'true',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
...additionalHeaders
|
||||
};
|
||||
}
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
static handleOptionsRequest(headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify({ message: 'OK' })
|
||||
};
|
||||
}
|
||||
|
||||
// 创建成功响应
|
||||
static successResponse(data, headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
}
|
||||
|
||||
// 创建错误响应
|
||||
static errorResponse(error, statusCode = 500, headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message || error
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// 验证HTTP方法
|
||||
static validateMethod(httpMethod, allowedMethods = ['POST']) {
|
||||
return allowedMethods.includes(httpMethod);
|
||||
}
|
||||
|
||||
// 解析请求体
|
||||
static parseBody(body) {
|
||||
try {
|
||||
return typeof body === 'string' ? JSON.parse(body) : body;
|
||||
} catch (error) {
|
||||
throw new Error('无效的请求体格式');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
console.log('=== GET_cloudflare_edukg_email 云函数开始执行 ===');
|
||||
console.log('接收到的事件参数:', JSON.stringify(event, null, 2));
|
||||
|
||||
try {
|
||||
// 解析HTTP请求
|
||||
const { httpMethod, body, headers } = event;
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
if (httpMethod === 'OPTIONS') {
|
||||
console.log('处理OPTIONS预检请求');
|
||||
return CorsUtils.handleOptionsRequest(headers);
|
||||
}
|
||||
|
||||
// 验证HTTP方法
|
||||
if (!CorsUtils.validateMethod(httpMethod, ['POST'])) {
|
||||
console.log('HTTP方法不允许:', httpMethod);
|
||||
return CorsUtils.errorResponse('方法不允许', 405, headers);
|
||||
}
|
||||
|
||||
// 解析请求体
|
||||
let requestData;
|
||||
try {
|
||||
requestData = CorsUtils.parseBody(body) || event;
|
||||
} catch (parseError) {
|
||||
console.error('请求体解析失败:', parseError);
|
||||
return CorsUtils.errorResponse(parseError, 400, headers);
|
||||
}
|
||||
|
||||
const { email } = requestData;
|
||||
|
||||
if (!email) {
|
||||
console.error('缺少必需的参数: email');
|
||||
return {
|
||||
success: false,
|
||||
error: '缺少邮箱地址参数'
|
||||
};
|
||||
}
|
||||
|
||||
console.log('查询邮箱:', email);
|
||||
|
||||
// 获取数据库引用
|
||||
const db = uniCloud.database();
|
||||
const collection = db.collection('cloudflare_edukg_email');
|
||||
|
||||
// 首先检查集合是否存在以及总数据量
|
||||
console.log('=== 开始数据库调试 ===');
|
||||
try {
|
||||
const countResult = await collection.count();
|
||||
console.log('数据库集合总记录数:', countResult.total);
|
||||
|
||||
// 获取前几条记录看看数据结构
|
||||
const sampleResult = await collection.limit(3).get();
|
||||
console.log('数据库样本数据:', JSON.stringify(sampleResult.data, null, 2));
|
||||
|
||||
// 检查是否有该邮箱的任何记录(不限制条件)
|
||||
const allEmailResult = await collection
|
||||
.where({
|
||||
emailTo: email
|
||||
})
|
||||
.get();
|
||||
console.log(`邮箱 ${email} 的所有记录数:`, allEmailResult.data.length);
|
||||
console.log(`邮箱 ${email} 的所有记录:`, JSON.stringify(allEmailResult.data, null, 2));
|
||||
|
||||
// 尝试模糊匹配
|
||||
const fuzzyResult = await collection
|
||||
.where({
|
||||
emailTo: db.RegExp({
|
||||
regexp: email.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
|
||||
options: 'i'
|
||||
})
|
||||
})
|
||||
.get();
|
||||
console.log(`邮箱 ${email} 的模糊匹配记录数:`, fuzzyResult.data.length);
|
||||
|
||||
} catch (debugError) {
|
||||
console.error('数据库调试查询失败:', debugError);
|
||||
}
|
||||
|
||||
// 尝试多种排序方式查询该邮箱的最新一条消息
|
||||
let result;
|
||||
|
||||
// 方式1:按emailDate排序
|
||||
try {
|
||||
result = await collection
|
||||
.where({
|
||||
emailTo: email
|
||||
})
|
||||
.orderBy('emailDate', 'desc')
|
||||
.limit(1)
|
||||
.get();
|
||||
console.log('按emailDate排序的查询结果:', result.data.length);
|
||||
} catch (sortError) {
|
||||
console.log('按emailDate排序失败:', sortError.message);
|
||||
}
|
||||
|
||||
// 如果方式1没有结果,尝试方式2:按createTime排序
|
||||
if (!result || !result.data || result.data.length === 0) {
|
||||
try {
|
||||
result = await collection
|
||||
.where({
|
||||
emailTo: email
|
||||
})
|
||||
.orderBy('createTime', 'desc')
|
||||
.limit(1)
|
||||
.get();
|
||||
console.log('按createTime排序的查询结果:', result.data.length);
|
||||
} catch (sortError) {
|
||||
console.log('按createTime排序失败:', sortError.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果方式2也没有结果,尝试方式3:不排序,直接查询
|
||||
if (!result || !result.data || result.data.length === 0) {
|
||||
try {
|
||||
result = await collection
|
||||
.where({
|
||||
emailTo: email
|
||||
})
|
||||
.limit(1)
|
||||
.get();
|
||||
console.log('不排序的查询结果:', result.data.length);
|
||||
} catch (sortError) {
|
||||
console.log('不排序查询失败:', sortError.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('数据库查询结果:', JSON.stringify(result, null, 2));
|
||||
console.log('查询结果数据长度:', result.data ? result.data.length : 0);
|
||||
|
||||
if (result.data && result.data.length > 0) {
|
||||
const latestEmail = result.data[0];
|
||||
console.log('找到最新邮件:', JSON.stringify(latestEmail, null, 2));
|
||||
|
||||
const responseData = {
|
||||
success: true,
|
||||
message: '成功获取最新邮件',
|
||||
data: latestEmail
|
||||
};
|
||||
|
||||
console.log('准备返回成功响应:', JSON.stringify(responseData, null, 2));
|
||||
console.log('=== 云函数执行成功 ===');
|
||||
|
||||
return CorsUtils.successResponse(responseData, headers);
|
||||
} else {
|
||||
console.log('未找到该邮箱的邮件');
|
||||
|
||||
const responseData = {
|
||||
success: false,
|
||||
message: '暂无邮件',
|
||||
data: null
|
||||
};
|
||||
|
||||
console.log('准备返回无邮件响应:', JSON.stringify(responseData, null, 2));
|
||||
return CorsUtils.successResponse(responseData, headers);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('=== GET_cloudflare_edukg_email 云函数执行失败 ===');
|
||||
console.error('错误详情:', error);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
|
||||
// 确保headers变量可用
|
||||
const headers = event?.headers || {};
|
||||
return CorsUtils.errorResponse(error, 500, headers);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "GET_cloudflare_edukg_email",
|
||||
"version": "1.0.0",
|
||||
"description": "获取cloudflare_edukg_email数据集中的最新邮件",
|
||||
"main": "index.js",
|
||||
"dependencies": {}
|
||||
}
|
||||
234
uniCloud/cloudfunctions/POST_cloudflare_edukg_email/index.js
Normal file
234
uniCloud/cloudfunctions/POST_cloudflare_edukg_email/index.js
Normal file
@@ -0,0 +1,234 @@
|
||||
'use strict';
|
||||
|
||||
// CORS工具函数
|
||||
class CorsUtils {
|
||||
// 获取请求来源
|
||||
static getOrigin(headers) {
|
||||
return headers?.origin || headers?.Origin || '*';
|
||||
}
|
||||
|
||||
// 设置CORS响应头
|
||||
static setCorsHeaders(origin, additionalHeaders = {}) {
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': origin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, Accept, Origin',
|
||||
'Access-Control-Allow-Credentials': 'true',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
...additionalHeaders
|
||||
};
|
||||
}
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
static handleOptionsRequest(headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify({ message: 'OK' })
|
||||
};
|
||||
}
|
||||
|
||||
// 创建成功响应
|
||||
static successResponse(data, headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
}
|
||||
|
||||
// 创建错误响应
|
||||
static errorResponse(error, statusCode = 500, headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message || error
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// 验证HTTP方法
|
||||
static validateMethod(httpMethod, allowedMethods = ['POST']) {
|
||||
return allowedMethods.includes(httpMethod);
|
||||
}
|
||||
|
||||
// 解析请求体
|
||||
static parseBody(body) {
|
||||
try {
|
||||
return typeof body === 'string' ? JSON.parse(body) : body;
|
||||
} catch (error) {
|
||||
throw new Error('无效的请求体格式');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
console.log('=== POST_cloudflare_edukg_email 云函数开始执行 ===');
|
||||
console.log('接收到的事件参数:', JSON.stringify(event, null, 2));
|
||||
|
||||
try {
|
||||
// 解析HTTP请求
|
||||
const { httpMethod, body, headers } = event;
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
if (httpMethod === 'OPTIONS') {
|
||||
console.log('处理OPTIONS预检请求');
|
||||
return CorsUtils.handleOptionsRequest(headers);
|
||||
}
|
||||
|
||||
// 验证HTTP方法
|
||||
if (!CorsUtils.validateMethod(httpMethod, ['POST'])) {
|
||||
console.log('HTTP方法不允许:', httpMethod);
|
||||
return CorsUtils.errorResponse('方法不允许', 405, headers);
|
||||
}
|
||||
|
||||
// 解析请求体
|
||||
let emailData;
|
||||
try {
|
||||
emailData = CorsUtils.parseBody(body) || event;
|
||||
} catch (parseError) {
|
||||
console.error('请求体解析失败:', parseError);
|
||||
return CorsUtils.errorResponse(parseError, 400, headers);
|
||||
}
|
||||
|
||||
console.log('解析后的邮件数据:', JSON.stringify(emailData, null, 2));
|
||||
|
||||
// 验证邮件数据结构
|
||||
const { emailInfo, emailContent } = emailData;
|
||||
|
||||
if (!emailInfo || !emailContent) {
|
||||
console.error('邮件数据格式错误 - 缺少必要字段');
|
||||
return CorsUtils.errorResponse('邮件数据格式错误', 400, headers);
|
||||
}
|
||||
|
||||
// 验证必要的邮件信息
|
||||
if (!emailInfo.from || !emailInfo.to) {
|
||||
console.error('邮件基本信息不完整');
|
||||
return CorsUtils.errorResponse('邮件基本信息不完整', 400, headers);
|
||||
}
|
||||
|
||||
console.log('=== 开始保存邮件到数据库 ===');
|
||||
console.log('邮件发件人:', emailInfo.from);
|
||||
console.log('邮件收件人:', emailInfo.to);
|
||||
console.log('邮件主题:', emailInfo.subject);
|
||||
console.log('邮件类型:', emailInfo.hasHtml ? 'HTML' : '纯文本');
|
||||
console.log('文本内容长度:', emailContent.text ? emailContent.text.length : 0);
|
||||
console.log('HTML内容长度:', emailContent.html ? emailContent.html.length : 0);
|
||||
|
||||
// 获取数据库引用
|
||||
const db = uniCloud.database();
|
||||
|
||||
// 准备保存的数据
|
||||
const emailRecord = {
|
||||
// 基本邮件信息
|
||||
emailFrom: emailInfo.from,
|
||||
emailTo: emailInfo.to,
|
||||
emailSubject: emailInfo.subject || '无主题',
|
||||
emailDate: emailInfo.date || new Date().toISOString(),
|
||||
|
||||
// 邮件内容
|
||||
emailText: emailContent.text || '',
|
||||
emailHtml: emailContent.html || '',
|
||||
|
||||
// 邮件类型和状态
|
||||
emailType: emailInfo.hasHtml ? 'html' : 'text',
|
||||
hasHtml: emailInfo.hasHtml || false,
|
||||
hasText: !!emailContent.text,
|
||||
|
||||
// 内容长度统计
|
||||
textLength: emailContent.text ? emailContent.text.length : 0,
|
||||
htmlLength: emailContent.html ? emailContent.html.length : 0,
|
||||
|
||||
// 处理信息
|
||||
createTime: Date.now(),
|
||||
processedAt: new Date().toISOString(),
|
||||
|
||||
// Worker 信息
|
||||
workerInfo: emailData.workerInfo || {
|
||||
version: '1.0.0',
|
||||
source: 'cloudflare-workers-email-parser'
|
||||
}
|
||||
};
|
||||
|
||||
console.log('准备保存的邮件记录:', JSON.stringify(emailRecord, null, 2));
|
||||
|
||||
try {
|
||||
// 保存到数据库
|
||||
const result = await db.collection('cloudflare_edukg_email').add(emailRecord);
|
||||
|
||||
console.log('✅ 邮件保存成功');
|
||||
console.log('数据库插入结果:', JSON.stringify(result, null, 2));
|
||||
|
||||
// 统计信息
|
||||
const stats = {
|
||||
insertedId: result.id,
|
||||
emailFrom: emailInfo.from,
|
||||
emailTo: emailInfo.to,
|
||||
subject: emailInfo.subject,
|
||||
contentType: emailInfo.hasHtml ? 'html' : 'text',
|
||||
textLength: emailContent.text ? emailContent.text.length : 0,
|
||||
htmlLength: emailContent.html ? emailContent.html.length : 0,
|
||||
processingTime: Date.now() - (emailData.startTime || Date.now())
|
||||
};
|
||||
|
||||
const responseData = {
|
||||
success: true,
|
||||
message: '邮件保存成功',
|
||||
insertedId: result.id,
|
||||
stats: stats,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
console.log('准备返回成功响应:', JSON.stringify(responseData, null, 2));
|
||||
console.log('=== 云函数执行成功 ===');
|
||||
|
||||
return CorsUtils.successResponse(responseData, headers);
|
||||
|
||||
} catch (dbError) {
|
||||
console.error('=== 数据库操作失败 ===');
|
||||
console.error('数据库错误详情:', dbError);
|
||||
console.error('错误类型:', dbError.constructor.name);
|
||||
console.error('错误消息:', dbError.message);
|
||||
|
||||
// 检查是否是集合不存在的错误
|
||||
if (dbError.error === -407 || dbError.errorMessage?.includes('not found collection')) {
|
||||
console.log('数据库集合不存在,尝试创建...');
|
||||
try {
|
||||
// 尝试再次插入,这会自动创建集合
|
||||
const retryResult = await db.collection('cloudflare_edukg_email').add(emailRecord);
|
||||
console.log('✅ 集合创建成功,邮件保存成功');
|
||||
|
||||
const responseData = {
|
||||
success: true,
|
||||
message: '邮件保存成功(集合已自动创建)',
|
||||
insertedId: retryResult.id,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return CorsUtils.successResponse(responseData, headers);
|
||||
} catch (retryError) {
|
||||
console.error('重试插入也失败:', retryError);
|
||||
throw retryError;
|
||||
}
|
||||
} else {
|
||||
throw dbError;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('=== POST_cloudflare_edukg_email 云函数执行失败 ===');
|
||||
console.error('错误详情:', error);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
console.error('错误类型:', error.constructor.name);
|
||||
|
||||
// 确保headers变量可用
|
||||
const headers = event?.headers || {};
|
||||
return CorsUtils.errorResponse(error, 500, headers);
|
||||
}
|
||||
};
|
||||
398
uniCloud/cloudfunctions/generate-email/index.js
Normal file
398
uniCloud/cloudfunctions/generate-email/index.js
Normal file
@@ -0,0 +1,398 @@
|
||||
'use strict';
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
// CORS工具函数
|
||||
class CorsUtils {
|
||||
// 获取请求来源
|
||||
static getOrigin(headers) {
|
||||
return headers?.origin || headers?.Origin || '*';
|
||||
}
|
||||
|
||||
// 设置CORS响应头
|
||||
static setCorsHeaders(origin, additionalHeaders = {}) {
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': origin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, Accept, Origin',
|
||||
'Access-Control-Allow-Credentials': 'true',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
...additionalHeaders
|
||||
};
|
||||
}
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
static handleOptionsRequest(headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify({ message: 'OK' })
|
||||
};
|
||||
}
|
||||
|
||||
// 创建成功响应
|
||||
static successResponse(data, headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
}
|
||||
|
||||
// 创建错误响应
|
||||
static errorResponse(error, statusCode = 500, headers) {
|
||||
const origin = this.getOrigin(headers);
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
headers: this.setCorsHeaders(origin),
|
||||
body: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message || error
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// 验证HTTP方法
|
||||
static validateMethod(httpMethod, allowedMethods = ['POST']) {
|
||||
return allowedMethods.includes(httpMethod);
|
||||
}
|
||||
|
||||
// 解析请求体
|
||||
static parseBody(body) {
|
||||
try {
|
||||
return typeof body === 'string' ? JSON.parse(body) : body;
|
||||
} catch (error) {
|
||||
throw new Error('无效的请求体格式');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 配置文件
|
||||
const config = {
|
||||
cloudflare: {
|
||||
api_token: "※※※※※※※※※※※※※※※※※※※※※※※※※※※※",
|
||||
zone_id: "※※※※※※※※※※※※※※※※※※※※※※※※※※",
|
||||
domain: "※※※※※※※※※"
|
||||
},
|
||||
workers: {
|
||||
// Cloudflare Workers 配置
|
||||
worker_name: "orange-paper-039a", // 你的Worker名称
|
||||
worker_route: "yydsoi.edu.kg", // Worker的路由域名,使用你的主域名
|
||||
use_worker_first: true // 只使用 Worker 方式
|
||||
}
|
||||
};
|
||||
|
||||
// 生成8位随机邮箱名
|
||||
function generateRandomEmailName() {
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < 8; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Cloudflare API操作
|
||||
class CloudflareAPI {
|
||||
constructor() {
|
||||
this.apiToken = config.cloudflare.api_token;
|
||||
this.zoneId = config.cloudflare.zone_id;
|
||||
this.domain = config.cloudflare.domain;
|
||||
this.baseURL = 'https://api.cloudflare.com/client/v4';
|
||||
}
|
||||
|
||||
// 创建邮箱路由
|
||||
async createEmailRoute(email) {
|
||||
try {
|
||||
// 验证配置
|
||||
this.validateConfig();
|
||||
|
||||
if (config.workers.use_worker_first) {
|
||||
console.log('使用 Worker 方式创建邮箱路由...');
|
||||
return await this.createWorkerRoute(email);
|
||||
} else {
|
||||
console.log('使用转发方式创建邮箱路由...');
|
||||
return await this.createForwardRoute(email);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建邮箱路由失败,详细错误:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证配置
|
||||
validateConfig() {
|
||||
console.log('=== 配置验证 ===');
|
||||
|
||||
// 检查必要的配置项
|
||||
if (!this.apiToken || this.apiToken === '') {
|
||||
throw new Error('API Token 未配置');
|
||||
}
|
||||
|
||||
if (!this.zoneId || this.zoneId === '') {
|
||||
throw new Error('Zone ID 未配置');
|
||||
}
|
||||
|
||||
if (!this.domain || this.domain === '') {
|
||||
throw new Error('域名未配置');
|
||||
}
|
||||
|
||||
if (!config.workers.worker_name || config.workers.worker_name === '') {
|
||||
throw new Error('Worker 名称未配置');
|
||||
}
|
||||
|
||||
if (!config.workers.worker_route || config.workers.worker_route === '') {
|
||||
throw new Error('Worker 路由域名未配置');
|
||||
}
|
||||
|
||||
console.log('✅ 基础配置验证通过');
|
||||
console.log('API Token 长度:', this.apiToken.length);
|
||||
console.log('Zone ID 格式:', this.zoneId);
|
||||
console.log('域名:', this.domain);
|
||||
console.log('Worker 名称:', config.workers.worker_name);
|
||||
console.log('Worker 路由域名:', config.workers.worker_route);
|
||||
}
|
||||
|
||||
// 创建转发路由(备选方案)
|
||||
async createForwardRoute(email) {
|
||||
// 这里需要一个真实的邮箱地址作为转发目标
|
||||
// 你需要在 Cloudflare 中验证这个邮箱地址
|
||||
const forwardToEmail = "admin@yydsoi.edu.kg"; // 请替换为你的真实邮箱
|
||||
|
||||
const payload = {
|
||||
name: `temp-forward-${Date.now()}`,
|
||||
enabled: true,
|
||||
matchers: [
|
||||
{
|
||||
type: 'literal',
|
||||
field: 'to',
|
||||
value: email
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: 'forward',
|
||||
value: [forwardToEmail]
|
||||
}
|
||||
],
|
||||
priority: 0
|
||||
};
|
||||
|
||||
console.log('=== 转发路由创建详情 ===');
|
||||
console.log('目标邮箱:', email);
|
||||
console.log('转发到:', forwardToEmail);
|
||||
console.log('请求体:', JSON.stringify(payload, null, 2));
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${this.baseURL}/zones/${this.zoneId}/email/routing/rules`,
|
||||
payload,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.apiToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.data.success) {
|
||||
throw new Error(`Cloudflare API错误: ${JSON.stringify(response.data.errors)}`);
|
||||
}
|
||||
|
||||
console.log('✅ 转发邮箱路由创建成功');
|
||||
return response.data.result;
|
||||
} catch (error) {
|
||||
console.error('转发路由创建失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建 Worker 路由
|
||||
async createWorkerRoute(email) {
|
||||
const payload = {
|
||||
name: `temp-${Date.now()}`,
|
||||
enabled: true,
|
||||
matchers: [
|
||||
{
|
||||
type: 'literal',
|
||||
field: 'to',
|
||||
value: email
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: 'worker',
|
||||
value: [config.workers.worker_name]
|
||||
}
|
||||
],
|
||||
priority: 0
|
||||
};
|
||||
|
||||
console.log('=== Worker 路由创建详情 ===');
|
||||
console.log('目标邮箱:', email);
|
||||
console.log('Worker 名称:', config.workers.worker_name);
|
||||
console.log('Worker 路由域名:', config.workers.worker_route);
|
||||
console.log('Zone ID:', this.zoneId);
|
||||
console.log('域名:', this.domain);
|
||||
console.log('API 基础URL:', this.baseURL);
|
||||
console.log('请求体:', JSON.stringify(payload, null, 2));
|
||||
console.log('请求头:', JSON.stringify({
|
||||
'Authorization': `Bearer ${this.apiToken.substring(0, 10)}...`,
|
||||
'Content-Type': 'application/json'
|
||||
}, null, 2));
|
||||
|
||||
try {
|
||||
console.log('开始发送请求到 Cloudflare API...');
|
||||
const response = await axios.post(
|
||||
`${this.baseURL}/zones/${this.zoneId}/email/routing/rules`,
|
||||
payload,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.apiToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log('=== Cloudflare API 响应详情 ===');
|
||||
console.log('响应状态码:', response.status);
|
||||
console.log('响应状态文本:', response.statusText);
|
||||
console.log('响应头:', JSON.stringify(response.headers, null, 2));
|
||||
console.log('响应数据:', JSON.stringify(response.data, null, 2));
|
||||
|
||||
if (!response.data.success) {
|
||||
console.error('Cloudflare API 返回失败状态');
|
||||
console.error('错误详情:', JSON.stringify(response.data.errors, null, 2));
|
||||
throw new Error(`Cloudflare API错误: ${JSON.stringify(response.data.errors)}`);
|
||||
}
|
||||
|
||||
console.log('✅ Worker 邮箱路由创建成功');
|
||||
return response.data.result;
|
||||
|
||||
} catch (error) {
|
||||
console.error('=== Worker 路由创建失败详情 ===');
|
||||
|
||||
if (error.response) {
|
||||
// 服务器响应了错误状态码
|
||||
console.error('错误响应状态码:', error.response.status);
|
||||
console.error('错误响应状态文本:', error.response.statusText);
|
||||
console.error('错误响应头:', JSON.stringify(error.response.headers, null, 2));
|
||||
console.error('错误响应数据:', JSON.stringify(error.response.data, null, 2));
|
||||
|
||||
// 特别关注 422 错误
|
||||
if (error.response.status === 422) {
|
||||
console.error('🔴 422 错误 - 请求格式正确但无法处理');
|
||||
console.error('可能的原因:');
|
||||
console.error('1. Worker 路由配置错误');
|
||||
console.error('2. 域名配置问题');
|
||||
console.error('3. API Token 权限不足');
|
||||
console.error('4. 邮箱路由规则冲突');
|
||||
console.error('5. 请求体格式不符合 API 要求');
|
||||
}
|
||||
} else if (error.request) {
|
||||
// 请求已发送但没有收到响应
|
||||
console.error('请求已发送但无响应:', error.request);
|
||||
} else {
|
||||
// 请求设置时出错
|
||||
console.error('请求设置错误:', error.message);
|
||||
}
|
||||
|
||||
console.error('完整错误对象:', error);
|
||||
console.error('错误类型:', error.constructor.name);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
try {
|
||||
console.log('=== 云函数开始执行 ===');
|
||||
console.log('请求方法:', event.httpMethod);
|
||||
console.log('请求头:', JSON.stringify(event.headers, null, 2));
|
||||
|
||||
// 解析HTTP请求
|
||||
const { httpMethod, body, headers } = event;
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
if (httpMethod === 'OPTIONS') {
|
||||
console.log('处理OPTIONS预检请求');
|
||||
return CorsUtils.handleOptionsRequest(headers);
|
||||
}
|
||||
|
||||
// 验证HTTP方法
|
||||
if (!CorsUtils.validateMethod(httpMethod, ['POST'])) {
|
||||
console.log('HTTP方法不允许:', httpMethod);
|
||||
return CorsUtils.methodNotAllowedResponse(['POST'], headers);
|
||||
}
|
||||
|
||||
console.log('开始生成临时邮箱...');
|
||||
|
||||
// 生成8位随机邮箱名
|
||||
const emailName = generateRandomEmailName();
|
||||
const tempEmail = `${emailName}@${config.cloudflare.domain}`;
|
||||
console.log('生成的临时邮箱:', tempEmail);
|
||||
|
||||
// 在Cloudflare中创建邮箱路由
|
||||
console.log('开始创建Cloudflare邮箱路由(使用Worker方式)...');
|
||||
const cloudflare = new CloudflareAPI();
|
||||
const cloudflareResult = await cloudflare.createEmailRoute(tempEmail);
|
||||
console.log('Cloudflare邮箱路由创建成功(使用Worker方式):', JSON.stringify(cloudflareResult, null, 2));
|
||||
|
||||
// 保存到数据库
|
||||
console.log('开始保存到数据库...');
|
||||
const db = uniCloud.database();
|
||||
try {
|
||||
const dbResult = await db.collection('temp_emails').add({
|
||||
email: tempEmail,
|
||||
createdAt: Date.now(),
|
||||
deleted: false
|
||||
});
|
||||
console.log('数据库保存成功:', JSON.stringify(dbResult, null, 2));
|
||||
} catch (dbError) {
|
||||
if (dbError.error === -407 || dbError.errorMessage?.includes('not found collection')) {
|
||||
console.log('数据库集合不存在,尝试创建集合...');
|
||||
try {
|
||||
const dbResult = await db.collection('temp_emails').add({
|
||||
email: tempEmail,
|
||||
createdAt: Date.now(),
|
||||
deleted: false
|
||||
});
|
||||
console.log('数据库集合创建成功:', JSON.stringify(dbResult, null, 2));
|
||||
} catch (createError) {
|
||||
console.error('创建数据库集合失败:', createError);
|
||||
console.log('数据库操作失败,但邮箱路由已创建,继续执行');
|
||||
}
|
||||
} else {
|
||||
throw dbError;
|
||||
}
|
||||
}
|
||||
|
||||
const responseData = {
|
||||
success: true,
|
||||
email: tempEmail,
|
||||
message: '临时邮箱创建成功',
|
||||
note: '邮箱路由已创建,邮件将使用Worker方式处理'
|
||||
};
|
||||
|
||||
console.log('准备返回成功响应:', JSON.stringify(responseData, null, 2));
|
||||
console.log('=== 云函数执行成功 ===');
|
||||
|
||||
return CorsUtils.successResponse(responseData, headers);
|
||||
} catch (error) {
|
||||
console.error('=== 云函数执行失败 ===');
|
||||
console.error('错误详情:', error);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
console.error('错误类型:', error.constructor.name);
|
||||
|
||||
// 确保headers变量可用
|
||||
const headers = event?.headers || {};
|
||||
return CorsUtils.errorResponse(error, 500, headers);
|
||||
}
|
||||
};
|
||||
9
uniCloud/cloudfunctions/generate-email/package.json
Normal file
9
uniCloud/cloudfunctions/generate-email/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "generate-email",
|
||||
"version": "1.0.0",
|
||||
"description": "生成临时邮箱云函数",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"axios": "^1.5.0"
|
||||
}
|
||||
}
|
||||
23
uniCloud/cloudfunctions/package.json
Normal file
23
uniCloud/cloudfunctions/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "temp-email-cloudfunctions",
|
||||
"version": "1.0.0",
|
||||
"description": "临时邮箱生成器云函数依赖包",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"temp-email",
|
||||
"cloudflare",
|
||||
"unicloud",
|
||||
"email-routing"
|
||||
],
|
||||
"author": "Your Name",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
}
|
||||
74
前端/index.html
Normal file
74
前端/index.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>临时邮箱生成器</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="main-container">
|
||||
<div class="container">
|
||||
<div class="logo">📧</div>
|
||||
<h1>临时邮箱生成器</h1>
|
||||
<p class="subtitle">一键生成安全的临时邮箱地址</p>
|
||||
|
||||
<button class="generate-btn" id="generateBtn" onclick="generateEmail()">
|
||||
<span id="btnText">生成新的临时邮箱</span>
|
||||
</button>
|
||||
|
||||
<div class="email-display" id="emailDisplay">
|
||||
<div class="email-label">您的临时邮箱地址</div>
|
||||
<div class="email-address" id="emailAddress"></div>
|
||||
<div class="button-group">
|
||||
<button class="copy-btn" onclick="copyEmail()">复制邮箱地址</button>
|
||||
<button class="view-email-btn" id="viewEmailBtn" onclick="viewLatestEmail()">查看邮件</button>
|
||||
<button class="clear-btn" onclick="clearCurrentEmail()">清除邮箱</button>
|
||||
</div>
|
||||
<div class="email-info" id="emailInfo"></div>
|
||||
</div>
|
||||
|
||||
<div class="email-content" id="emailContent">
|
||||
<div class="email-header">
|
||||
<h3>最新邮件内容
|
||||
<button class="refresh-btn" onclick="viewLatestEmail()">刷新</button>
|
||||
</h3>
|
||||
</div>
|
||||
<div id="emailDetails"></div>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status"></div>
|
||||
|
||||
<div class="features">
|
||||
<div class="feature-item">
|
||||
<span class="feature-icon">🔒</span>
|
||||
<span>安全可靠,保护您的隐私</span>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<span class="feature-icon">⚡</span>
|
||||
<span>即时生成,无需等待</span>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<span class="feature-icon">🔄</span>
|
||||
<span>可重复使用,灵活便捷</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 作者信息区域 -->
|
||||
<div class="author-section">
|
||||
<h2 class="author-title">联系作者</h2>
|
||||
<div class="wechat-qr">
|
||||
<img src="wechat-qr.jpg" alt="作者微信二维码" class="qr-image">
|
||||
</div>
|
||||
<div class="author-info">
|
||||
<p>扫描上方二维码</p>
|
||||
<p>添加微信好友</p>
|
||||
<p class="highlight">获取技术支持</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
663
前端/script.js
Normal file
663
前端/script.js
Normal file
@@ -0,0 +1,663 @@
|
||||
// 全局变量存储当前生成的邮箱
|
||||
let currentEmail = null;
|
||||
|
||||
async function generateEmail() {
|
||||
const btn = document.getElementById('generateBtn');
|
||||
const btnText = document.getElementById('btnText');
|
||||
const emailDisplay = document.getElementById('emailDisplay');
|
||||
const emailContent = document.getElementById('emailContent');
|
||||
const status = document.getElementById('status');
|
||||
|
||||
console.log('=== 前端开始生成邮箱 ===');
|
||||
console.log('当前邮箱:', currentEmail);
|
||||
|
||||
// 禁用按钮并显示加载状态
|
||||
btn.disabled = true;
|
||||
btnText.innerHTML = '<span class="loading"></span>生成中...';
|
||||
|
||||
// 清除之前的所有显示内容
|
||||
try {
|
||||
status.style.display = 'none';
|
||||
emailDisplay.classList.remove('show');
|
||||
|
||||
// 安全地处理emailContent元素
|
||||
if (emailContent) {
|
||||
emailContent.classList.remove('show');
|
||||
} else {
|
||||
console.warn('emailContent元素未找到');
|
||||
}
|
||||
|
||||
// 清除邮箱地址显示
|
||||
const emailAddressElement = document.getElementById('emailAddress');
|
||||
if (emailAddressElement) {
|
||||
emailAddressElement.textContent = '';
|
||||
}
|
||||
|
||||
// 清除本地存储的旧邮箱(强制生成新邮箱)
|
||||
currentEmail = null;
|
||||
localStorage.removeItem('tempEmail');
|
||||
localStorage.removeItem('tempEmailCreatedAt');
|
||||
|
||||
console.log('已清除旧邮箱数据,准备生成新邮箱');
|
||||
} catch (clearError) {
|
||||
console.error('清除旧数据时出错:', clearError);
|
||||
// 继续执行,不要因为清除错误而中断生成流程
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('准备发送请求到云函数...');
|
||||
const requestBody = {};
|
||||
console.log('请求体:', JSON.stringify(requestBody, null, 2));
|
||||
|
||||
const response = await fetch('云函数链接generate-email', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody)
|
||||
});
|
||||
|
||||
console.log('收到响应:', response);
|
||||
console.log('响应状态:', response.status);
|
||||
console.log('响应状态文本:', response.statusText);
|
||||
console.log('响应头:', Object.fromEntries(response.headers.entries()));
|
||||
|
||||
const responseText = await response.text();
|
||||
console.log('响应文本:', responseText);
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(responseText);
|
||||
console.log('解析后的响应数据:', JSON.stringify(data, null, 2));
|
||||
|
||||
// 检查是否存在嵌套的JSON字符串
|
||||
if (data.body && typeof data.body === 'string') {
|
||||
console.log('检测到嵌套JSON字符串,进行二次解析...');
|
||||
try {
|
||||
const innerData = JSON.parse(data.body);
|
||||
console.log('二次解析后的数据:', JSON.stringify(innerData, null, 2));
|
||||
data = innerData; // 使用解析后的内部数据
|
||||
} catch (innerParseError) {
|
||||
console.error('嵌套JSON解析失败:', innerParseError);
|
||||
throw new Error('嵌套响应数据格式错误');
|
||||
}
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('JSON解析失败:', parseError);
|
||||
throw new Error(`响应格式错误: ${responseText}`);
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
console.log('云函数返回成功状态');
|
||||
|
||||
// 保存邮箱到全局变量和本地存储
|
||||
currentEmail = data.email;
|
||||
localStorage.setItem('tempEmail', data.email);
|
||||
localStorage.setItem('tempEmailCreatedAt', Date.now().toString());
|
||||
|
||||
console.log('邮箱已保存到全局变量:', currentEmail);
|
||||
console.log('邮箱已保存到本地存储');
|
||||
|
||||
// 显示生成的邮箱
|
||||
document.getElementById('emailAddress').textContent = data.email;
|
||||
emailDisplay.classList.add('show');
|
||||
|
||||
// 更新邮箱状态信息
|
||||
updateEmailInfo(data.email, Date.now());
|
||||
|
||||
// 显示成功状态
|
||||
const message = data.note ? `${data.message} - ${data.note}` : data.message;
|
||||
console.log('显示成功消息:', message);
|
||||
showStatus(message, 'success');
|
||||
|
||||
// 触发邮箱生成成功事件,供其他逻辑使用
|
||||
window.dispatchEvent(new CustomEvent('emailGenerated', {
|
||||
detail: {
|
||||
email: data.email,
|
||||
timestamp: Date.now(),
|
||||
success: true
|
||||
}
|
||||
}));
|
||||
|
||||
} else {
|
||||
console.error('云函数返回失败状态:', data);
|
||||
throw new Error(data.error || '生成失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('=== 前端执行失败 ===');
|
||||
console.error('错误详情:', error);
|
||||
console.error('错误类型:', error.constructor.name);
|
||||
console.error('错误消息:', error.message);
|
||||
|
||||
let errorMessage = '生成失败';
|
||||
if (error.message) {
|
||||
errorMessage += `: ${error.message}`;
|
||||
}
|
||||
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
||||
errorMessage = '网络连接失败,请检查网络或稍后重试';
|
||||
}
|
||||
console.log('显示错误消息:', errorMessage);
|
||||
showStatus(errorMessage, 'error');
|
||||
|
||||
// 触发邮箱生成失败事件
|
||||
window.dispatchEvent(new CustomEvent('emailGenerated', {
|
||||
detail: {
|
||||
email: null,
|
||||
timestamp: Date.now(),
|
||||
success: false,
|
||||
error: error.message
|
||||
}
|
||||
}));
|
||||
} finally {
|
||||
// 恢复按钮状态
|
||||
btn.disabled = false;
|
||||
btnText.textContent = '生成新的临时邮箱';
|
||||
console.log('=== 前端执行完成 ===');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前邮箱的函数
|
||||
function getCurrentEmail() {
|
||||
return currentEmail || localStorage.getItem('tempEmail');
|
||||
}
|
||||
|
||||
// 检查是否有有效的邮箱
|
||||
function hasValidEmail() {
|
||||
const email = getCurrentEmail();
|
||||
if (!email) return false;
|
||||
|
||||
// 检查邮箱是否在24小时内创建
|
||||
const createdAt = localStorage.getItem('tempEmailCreatedAt');
|
||||
if (createdAt) {
|
||||
const age = Date.now() - parseInt(createdAt);
|
||||
const oneDay = 24 * 60 * 60 * 1000;
|
||||
return age < oneDay;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 清除当前邮箱
|
||||
async function clearCurrentEmail() {
|
||||
const currentEmailAddress = getCurrentEmail();
|
||||
if (!currentEmailAddress) {
|
||||
showStatus('没有可清除的邮箱', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 确认删除
|
||||
if (!confirm(`确定要删除邮箱 ${currentEmailAddress} 吗?\n\n此操作将:\n- 删除Cloudflare中的邮箱路由\n- 删除数据库中的所有邮件记录\n- 删除临时邮箱记录\n\n此操作不可撤销!`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clearBtn = document.querySelector('.clear-btn');
|
||||
const originalText = clearBtn.textContent;
|
||||
|
||||
console.log('=== 开始删除邮箱 ===');
|
||||
console.log('删除邮箱:', currentEmailAddress);
|
||||
|
||||
// 禁用按钮并显示加载状态
|
||||
clearBtn.disabled = true;
|
||||
clearBtn.innerHTML = '<span class="loading"></span>删除中...';
|
||||
|
||||
try {
|
||||
console.log('准备发送请求到Delete_edu_cloudfare云函数...');
|
||||
const requestBody = {
|
||||
email: currentEmailAddress
|
||||
};
|
||||
console.log('请求体:', JSON.stringify(requestBody, null, 2));
|
||||
|
||||
const response = await fetch('云函数链接Delete_edu_cloudfare', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody)
|
||||
});
|
||||
|
||||
console.log('收到响应:', response);
|
||||
console.log('响应状态:', response.status);
|
||||
|
||||
const responseText = await response.text();
|
||||
console.log('响应文本:', responseText);
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(responseText);
|
||||
console.log('解析后的响应数据:', JSON.stringify(data, null, 2));
|
||||
|
||||
// 检查是否存在嵌套的JSON字符串
|
||||
if (data.body && typeof data.body === 'string') {
|
||||
console.log('检测到嵌套JSON字符串,进行二次解析...');
|
||||
try {
|
||||
const innerData = JSON.parse(data.body);
|
||||
console.log('二次解析后的数据:', JSON.stringify(innerData, null, 2));
|
||||
data = innerData;
|
||||
} catch (innerParseError) {
|
||||
console.error('嵌套JSON解析失败:', innerParseError);
|
||||
throw new Error('嵌套响应数据格式错误');
|
||||
}
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('JSON解析失败:', parseError);
|
||||
throw new Error(`响应格式错误: ${responseText}`);
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
console.log('邮箱删除成功');
|
||||
|
||||
// 清除本地数据
|
||||
currentEmail = null;
|
||||
localStorage.removeItem('tempEmail');
|
||||
localStorage.removeItem('tempEmailCreatedAt');
|
||||
|
||||
// 隐藏显示区域
|
||||
document.getElementById('emailDisplay').classList.remove('show');
|
||||
document.getElementById('emailContent').classList.remove('show');
|
||||
|
||||
// 显示详细的删除结果
|
||||
let successMessage = data.message;
|
||||
if (data.summary) {
|
||||
successMessage += `\n详细信息:\n- Cloudflare路由: ${data.summary.cloudflareRoutes} 个\n- 邮件记录: ${data.summary.emailRecords} 条\n- 邮箱记录: ${data.summary.tempEmailRecords} 条`;
|
||||
}
|
||||
|
||||
showStatus(successMessage, 'success');
|
||||
console.log('邮箱已完全清除');
|
||||
|
||||
} else {
|
||||
console.error('邮箱删除失败:', data);
|
||||
let errorMessage = data.error || data.message || '删除失败';
|
||||
|
||||
// 显示详细的错误信息
|
||||
if (data.details) {
|
||||
errorMessage += '\n详细错误:';
|
||||
if (!data.details.cloudflare.success) {
|
||||
errorMessage += `\n- Cloudflare: ${data.details.cloudflare.message}`;
|
||||
}
|
||||
if (!data.details.emailData.success) {
|
||||
errorMessage += `\n- 邮件数据: ${data.details.emailData.message}`;
|
||||
}
|
||||
if (!data.details.tempEmails.success) {
|
||||
errorMessage += `\n- 邮箱记录: ${data.details.tempEmails.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
showStatus(errorMessage, 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('=== 删除邮箱失败 ===');
|
||||
console.error('错误详情:', error);
|
||||
|
||||
let errorMessage = '删除邮箱失败';
|
||||
if (error.message) {
|
||||
errorMessage += `: ${error.message}`;
|
||||
}
|
||||
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
||||
errorMessage = '网络连接失败,请检查网络或稍后重试';
|
||||
}
|
||||
|
||||
console.log('显示错误消息:', errorMessage);
|
||||
showStatus(errorMessage, 'error');
|
||||
|
||||
} finally {
|
||||
// 恢复按钮状态
|
||||
clearBtn.disabled = false;
|
||||
clearBtn.textContent = originalText;
|
||||
console.log('=== 删除邮箱操作完成 ===');
|
||||
}
|
||||
}
|
||||
|
||||
function showStatus(message, type) {
|
||||
const status = document.getElementById('status');
|
||||
status.textContent = message;
|
||||
status.className = `status ${type}`;
|
||||
status.style.display = 'block';
|
||||
|
||||
// 3秒后自动隐藏
|
||||
setTimeout(() => {
|
||||
status.style.display = 'none';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
async function copyEmail() {
|
||||
const emailAddress = document.getElementById('emailAddress').textContent;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(emailAddress);
|
||||
showStatus('邮箱地址已复制到剪贴板!', 'success');
|
||||
} catch (error) {
|
||||
// 降级方案:使用传统方法复制
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = emailAddress;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
showStatus('邮箱地址已复制到剪贴板!', 'success');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新邮箱状态信息的函数
|
||||
function updateEmailInfo(email, timestamp) {
|
||||
const emailInfo = document.getElementById('emailInfo');
|
||||
emailInfo.textContent = `邮箱地址: ${email}\n创建时间: ${new Date(timestamp).toLocaleString()}`;
|
||||
emailInfo.className = 'email-info'; // 确保默认样式
|
||||
if (hasValidEmail()) {
|
||||
emailInfo.classList.add('valid');
|
||||
emailInfo.classList.remove('expired');
|
||||
} else {
|
||||
emailInfo.classList.add('expired');
|
||||
emailInfo.classList.remove('valid');
|
||||
}
|
||||
}
|
||||
|
||||
// 查看最新邮件的函数
|
||||
async function viewLatestEmail() {
|
||||
const currentEmailAddress = getCurrentEmail();
|
||||
if (!currentEmailAddress) {
|
||||
showStatus('请先生成邮箱地址', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const viewEmailBtn = document.getElementById('viewEmailBtn');
|
||||
const emailContent = document.getElementById('emailContent');
|
||||
const emailDetails = document.getElementById('emailDetails');
|
||||
|
||||
console.log('=== 开始查看邮件 ===');
|
||||
console.log('查询邮箱:', currentEmailAddress);
|
||||
|
||||
// 禁用按钮并显示加载状态
|
||||
const originalText = viewEmailBtn.textContent;
|
||||
viewEmailBtn.disabled = true;
|
||||
viewEmailBtn.innerHTML = '<span class="loading"></span>查询中...';
|
||||
|
||||
try {
|
||||
console.log('准备发送请求到GET_cloudflare_edukg_email云函数...');
|
||||
const requestBody = {
|
||||
email: currentEmailAddress
|
||||
};
|
||||
console.log('请求体:', JSON.stringify(requestBody, null, 2));
|
||||
|
||||
const response = await fetch('云函数链接GET_cloudflare_edukg_email', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody)
|
||||
});
|
||||
|
||||
console.log('收到响应:', response);
|
||||
console.log('响应状态:', response.status);
|
||||
|
||||
const responseText = await response.text();
|
||||
console.log('响应文本:', responseText);
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(responseText);
|
||||
console.log('解析后的响应数据:', JSON.stringify(data, null, 2));
|
||||
|
||||
// 检查是否存在嵌套的JSON字符串
|
||||
if (data.body && typeof data.body === 'string') {
|
||||
console.log('检测到嵌套JSON字符串,进行二次解析...');
|
||||
try {
|
||||
const innerData = JSON.parse(data.body);
|
||||
console.log('二次解析后的数据:', JSON.stringify(innerData, null, 2));
|
||||
data = innerData;
|
||||
} catch (innerParseError) {
|
||||
console.error('嵌套JSON解析失败:', innerParseError);
|
||||
throw new Error('嵌套响应数据格式错误');
|
||||
}
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('JSON解析失败:', parseError);
|
||||
throw new Error(`响应格式错误: ${responseText}`);
|
||||
}
|
||||
|
||||
if (data.success && data.data) {
|
||||
console.log('成功获取邮件数据');
|
||||
displayEmailContent(data.data);
|
||||
emailContent.classList.add('show');
|
||||
showStatus('成功获取最新邮件', 'success');
|
||||
} else {
|
||||
console.log('未找到邮件或查询失败:', data.message);
|
||||
displayNoEmailMessage(data.message || '暂无邮件');
|
||||
emailContent.classList.add('show');
|
||||
showStatus(data.message || '暂无邮件', 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('=== 查看邮件失败 ===');
|
||||
console.error('错误详情:', error);
|
||||
|
||||
let errorMessage = '查询邮件失败';
|
||||
if (error.message) {
|
||||
errorMessage += `: ${error.message}`;
|
||||
}
|
||||
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
||||
errorMessage = '网络连接失败,请检查网络或稍后重试';
|
||||
}
|
||||
|
||||
console.log('显示错误消息:', errorMessage);
|
||||
showStatus(errorMessage, 'error');
|
||||
displayNoEmailMessage(errorMessage);
|
||||
emailContent.classList.add('show');
|
||||
|
||||
} finally {
|
||||
// 恢复按钮状态
|
||||
viewEmailBtn.disabled = false;
|
||||
viewEmailBtn.textContent = originalText;
|
||||
console.log('=== 查看邮件完成 ===');
|
||||
}
|
||||
}
|
||||
|
||||
// 显示邮件内容的函数
|
||||
function displayEmailContent(emailData) {
|
||||
const emailDetails = document.getElementById('emailDetails');
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
try {
|
||||
return new Date(dateString).toLocaleString('zh-CN');
|
||||
} catch (e) {
|
||||
return dateString;
|
||||
}
|
||||
};
|
||||
|
||||
const emailHtml = `
|
||||
<div class="email-field">
|
||||
<span class="email-field-label">主题:</span>
|
||||
<span class="email-field-value">${emailData.emailSubject || '无主题'}</span>
|
||||
</div>
|
||||
<div class="email-field">
|
||||
<span class="email-field-label">发件人:</span>
|
||||
<span class="email-field-value">${emailData.emailFrom || '未知'}</span>
|
||||
</div>
|
||||
<div class="email-field">
|
||||
<span class="email-field-label">收件人:</span>
|
||||
<span class="email-field-value">${emailData.emailTo || '未知'}</span>
|
||||
</div>
|
||||
<div class="email-field">
|
||||
<span class="email-field-label">时间:</span>
|
||||
<span class="email-field-value">${formatDate(emailData.emailDate)}</span>
|
||||
</div>
|
||||
<div class="email-field">
|
||||
<span class="email-field-label">类型:</span>
|
||||
<span class="email-field-value">${emailData.emailType || '普通邮件'}</span>
|
||||
</div>
|
||||
<div class="email-body">
|
||||
${emailData.emailHtml ?
|
||||
`<iframe srcdoc="${emailData.emailHtml.replace(/"/g, '"')}" style="width: 100%; min-height: 300px; border: none;"></iframe>` :
|
||||
`<div class="email-text">${emailData.emailText || '无邮件内容'}</div>`
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
|
||||
emailDetails.innerHTML = emailHtml;
|
||||
}
|
||||
|
||||
// 显示无邮件消息的函数
|
||||
function displayNoEmailMessage(message) {
|
||||
const emailDetails = document.getElementById('emailDetails');
|
||||
emailDetails.innerHTML = `<div class="no-email-message">${message}</div>`;
|
||||
}
|
||||
|
||||
// 响应式布局调整函数
|
||||
function handleResponsiveLayout() {
|
||||
const container = document.querySelector('.main-container');
|
||||
const authorSection = document.querySelector('.author-section');
|
||||
const screenWidth = window.innerWidth;
|
||||
|
||||
// 根据屏幕宽度动态调整布局
|
||||
if (screenWidth < 768) {
|
||||
// 小屏幕:垂直布局
|
||||
container.style.flexDirection = 'column';
|
||||
if (authorSection) {
|
||||
authorSection.style.width = '100%';
|
||||
authorSection.style.maxWidth = '500px';
|
||||
authorSection.style.margin = '0 auto';
|
||||
}
|
||||
} else if (screenWidth < 992) {
|
||||
// 中等屏幕:垂直布局,但限制最大宽度
|
||||
container.style.flexDirection = 'column';
|
||||
if (authorSection) {
|
||||
authorSection.style.width = '100%';
|
||||
authorSection.style.maxWidth = '400px';
|
||||
authorSection.style.margin = '0 auto';
|
||||
}
|
||||
} else {
|
||||
// 大屏幕:水平布局
|
||||
container.style.flexDirection = 'row';
|
||||
if (authorSection) {
|
||||
authorSection.style.width = screenWidth > 1200 ? '320px' : '280px';
|
||||
authorSection.style.maxWidth = 'none';
|
||||
authorSection.style.margin = '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 优化触摸设备体验
|
||||
function handleTouchOptimization() {
|
||||
if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
|
||||
// 为触摸设备增加更大的点击区域
|
||||
const buttons = document.querySelectorAll('button');
|
||||
buttons.forEach(button => {
|
||||
button.style.minHeight = '44px'; // iOS推荐的最小触摸目标
|
||||
button.style.padding = '12px 20px';
|
||||
});
|
||||
|
||||
// 优化按钮组在触摸设备上的间距
|
||||
const buttonGroup = document.querySelector('.button-group');
|
||||
if (buttonGroup && window.innerWidth < 768) {
|
||||
buttonGroup.style.gap = '12px';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理设备方向变化
|
||||
function handleOrientationChange() {
|
||||
setTimeout(() => {
|
||||
handleResponsiveLayout();
|
||||
handleTouchOptimization();
|
||||
}, 100); // 给设备时间完成方向切换
|
||||
}
|
||||
|
||||
// 页面加载完成后的初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('临时邮箱生成器已加载');
|
||||
|
||||
// 检查是否有保存的邮箱
|
||||
const savedEmail = localStorage.getItem('tempEmail');
|
||||
if (savedEmail && hasValidEmail()) {
|
||||
currentEmail = savedEmail;
|
||||
document.getElementById('emailAddress').textContent = savedEmail;
|
||||
document.getElementById('emailDisplay').classList.add('show');
|
||||
|
||||
// 显示保存的邮箱状态信息
|
||||
const savedTimestamp = localStorage.getItem('tempEmailCreatedAt');
|
||||
if (savedTimestamp) {
|
||||
updateEmailInfo(savedEmail, parseInt(savedTimestamp));
|
||||
}
|
||||
|
||||
console.log('恢复保存的邮箱:', savedEmail);
|
||||
}
|
||||
|
||||
// 监听邮箱生成事件
|
||||
window.addEventListener('emailGenerated', function(event) {
|
||||
console.log('邮箱生成事件触发:', event.detail);
|
||||
// 这里可以添加其他逻辑,比如通知其他组件、记录日志等
|
||||
});
|
||||
|
||||
// 初始化响应式布局
|
||||
handleResponsiveLayout();
|
||||
handleTouchOptimization();
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResponsiveLayout);
|
||||
|
||||
// 监听设备方向变化
|
||||
window.addEventListener('orientationchange', handleOrientationChange);
|
||||
|
||||
// 监听视窗大小变化(用于处理虚拟键盘等情况)
|
||||
if (window.visualViewport) {
|
||||
window.visualViewport.addEventListener('resize', () => {
|
||||
// 处理移动设备虚拟键盘弹出时的布局调整
|
||||
if (window.visualViewport.height < window.innerHeight * 0.7) {
|
||||
document.body.style.height = `${window.visualViewport.height}px`;
|
||||
} else {
|
||||
document.body.style.height = 'auto';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 添加键盘导航支持
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// 支持Tab键在按钮间切换
|
||||
if (e.key === 'Tab') {
|
||||
const focusableElements = document.querySelectorAll('button:not(:disabled), [tabindex]:not([tabindex="-1"])');
|
||||
const currentIndex = Array.from(focusableElements).indexOf(document.activeElement);
|
||||
|
||||
if (e.shiftKey) {
|
||||
// Shift+Tab:向前切换
|
||||
if (currentIndex > 0) {
|
||||
focusableElements[currentIndex - 1].focus();
|
||||
} else {
|
||||
focusableElements[focusableElements.length - 1].focus();
|
||||
}
|
||||
} else {
|
||||
// Tab:向后切换
|
||||
if (currentIndex < focusableElements.length - 1) {
|
||||
focusableElements[currentIndex + 1].focus();
|
||||
} else {
|
||||
focusableElements[0].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 支持Enter键激活当前焦点按钮
|
||||
if (e.key === 'Enter' && document.activeElement.tagName === 'BUTTON') {
|
||||
e.preventDefault();
|
||||
document.activeElement.click();
|
||||
}
|
||||
});
|
||||
|
||||
// 添加无障碍支持
|
||||
const buttons = document.querySelectorAll('button');
|
||||
buttons.forEach(button => {
|
||||
// 为按钮添加合适的ARIA标签
|
||||
if (!button.getAttribute('aria-label')) {
|
||||
button.setAttribute('aria-label', button.textContent.trim());
|
||||
}
|
||||
|
||||
// 为禁用状态的按钮添加说明
|
||||
const observer = new MutationObserver(() => {
|
||||
if (button.disabled) {
|
||||
button.setAttribute('aria-disabled', 'true');
|
||||
} else {
|
||||
button.removeAttribute('aria-disabled');
|
||||
}
|
||||
});
|
||||
observer.observe(button, { attributes: true, attributeFilter: ['disabled'] });
|
||||
});
|
||||
|
||||
console.log('响应式优化和无障碍功能已初始化');
|
||||
});
|
||||
727
前端/style.css
Normal file
727
前端/style.css
Normal file
@@ -0,0 +1,727 @@
|
||||
/* 基础重置和字体设置 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
color: #1d1d1f;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 主容器布局 */
|
||||
.main-container {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
max-width: 1400px;
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* 主功能区域 */
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border-radius: 24px;
|
||||
padding: 48px;
|
||||
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.18);
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 作者信息区域 */
|
||||
.author-section {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border-radius: 24px;
|
||||
padding: 32px;
|
||||
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.18);
|
||||
width: 280px;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 标题和LOGO */
|
||||
.logo {
|
||||
font-size: clamp(2rem, 5vw, 3rem);
|
||||
margin-bottom: 16px;
|
||||
background: linear-gradient(135deg, #007AFF, #5856D6);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(1.8rem, 4vw, 2.5rem);
|
||||
font-weight: 700;
|
||||
color: #1d1d1f;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #86868b;
|
||||
margin-bottom: 40px;
|
||||
font-size: clamp(1rem, 2.5vw, 1.2rem);
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.generate-btn {
|
||||
background: linear-gradient(135deg, #007AFF 0%, #5856D6 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: clamp(12px, 3vw, 16px) clamp(32px, 8vw, 48px);
|
||||
font-size: clamp(1rem, 2.5vw, 1.1rem);
|
||||
font-weight: 600;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
margin-bottom: 32px;
|
||||
box-shadow: 0 4px 20px rgba(0, 122, 255, 0.3);
|
||||
font-family: inherit;
|
||||
letter-spacing: 0.01em;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.generate-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 32px rgba(0, 122, 255, 0.4);
|
||||
}
|
||||
|
||||
.generate-btn:active {
|
||||
transform: translateY(0);
|
||||
transition-duration: 0.1s;
|
||||
}
|
||||
|
||||
.generate-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* 邮箱显示区域 */
|
||||
.email-display {
|
||||
background: rgba(248, 248, 248, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 16px;
|
||||
padding: clamp(16px, 4vw, 24px);
|
||||
margin: 24px 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.email-display.show {
|
||||
display: block;
|
||||
animation: slideIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
.email-label {
|
||||
color: #86868b;
|
||||
font-size: clamp(0.8rem, 2vw, 0.9rem);
|
||||
margin-bottom: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.email-address {
|
||||
font-size: clamp(1.1rem, 3vw, 1.4rem);
|
||||
color: #007AFF;
|
||||
font-weight: 600;
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
word-break: break-all;
|
||||
background: rgba(0, 122, 255, 0.05);
|
||||
padding: clamp(8px, 2vw, 12px) clamp(12px, 3vw, 16px);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* 按钮组 */
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: clamp(8px, 2vw, 12px);
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.copy-btn, .clear-btn, .view-email-btn {
|
||||
border: none;
|
||||
padding: clamp(8px, 2vw, 10px) clamp(16px, 4vw, 20px);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: clamp(0.8rem, 2vw, 0.9rem);
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
font-family: inherit;
|
||||
backdrop-filter: blur(10px);
|
||||
min-width: 100px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: rgba(52, 199, 89, 0.9);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(52, 199, 89, 0.3);
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: rgba(52, 199, 89, 1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.view-email-btn {
|
||||
background: rgba(0, 122, 255, 0.9);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(0, 122, 255, 0.3);
|
||||
}
|
||||
|
||||
.view-email-btn:hover {
|
||||
background: rgba(0, 122, 255, 1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
background: rgba(255, 59, 48, 0.9);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(255, 59, 48, 0.3);
|
||||
}
|
||||
|
||||
.clear-btn:hover {
|
||||
background: rgba(255, 59, 48, 1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.copy-btn:disabled, .view-email-btn:disabled, .clear-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* 邮箱信息状态 */
|
||||
.email-info {
|
||||
margin-top: 16px;
|
||||
padding: 12px 16px;
|
||||
background: rgba(142, 142, 147, 0.1);
|
||||
border-radius: 8px;
|
||||
font-size: clamp(0.8rem, 2vw, 0.85rem);
|
||||
color: #86868b;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(142, 142, 147, 0.2);
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.email-info.valid {
|
||||
background: rgba(52, 199, 89, 0.1);
|
||||
border-color: rgba(52, 199, 89, 0.3);
|
||||
color: #30d158;
|
||||
}
|
||||
|
||||
.email-info.expired {
|
||||
background: rgba(255, 59, 48, 0.1);
|
||||
border-color: rgba(255, 59, 48, 0.3);
|
||||
color: #ff3b30;
|
||||
}
|
||||
|
||||
/* 状态消息 */
|
||||
.status {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-radius: 12px;
|
||||
display: none;
|
||||
font-weight: 500;
|
||||
font-size: clamp(0.85rem, 2vw, 0.95rem);
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.status.success {
|
||||
background: rgba(52, 199, 89, 0.1);
|
||||
color: #30d158;
|
||||
border: 1px solid rgba(52, 199, 89, 0.3);
|
||||
}
|
||||
|
||||
.status.error {
|
||||
background: rgba(255, 59, 48, 0.1);
|
||||
color: #ff3b30;
|
||||
border: 1px solid rgba(255, 59, 48, 0.3);
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid #ffffff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* 动画定义 */
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 功能特性列表 */
|
||||
.features {
|
||||
margin-top: 40px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 16px 0;
|
||||
color: #515154;
|
||||
font-size: clamp(0.85rem, 2vw, 0.95rem);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
color: #007AFF;
|
||||
margin-right: 16px;
|
||||
font-size: clamp(1rem, 2.5vw, 1.1rem);
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 邮件内容区域 */
|
||||
.email-content {
|
||||
background: rgba(248, 248, 248, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 16px;
|
||||
padding: clamp(16px, 4vw, 24px);
|
||||
margin: 24px 0;
|
||||
display: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.email-content.show {
|
||||
display: block;
|
||||
animation: slideIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
.email-header {
|
||||
border-bottom: 1px solid rgba(142, 142, 147, 0.2);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.email-header h3 {
|
||||
color: #1d1d1f;
|
||||
font-size: clamp(1.1rem, 3vw, 1.3rem);
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.email-field {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.email-field-label {
|
||||
font-weight: 600;
|
||||
color: #1d1d1f;
|
||||
display: inline-block;
|
||||
min-width: 80px;
|
||||
font-size: clamp(0.8rem, 2vw, 0.9rem);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.email-field-value {
|
||||
color: #515154;
|
||||
word-break: break-all;
|
||||
flex: 1;
|
||||
font-size: clamp(0.8rem, 2vw, 0.9rem);
|
||||
}
|
||||
|
||||
.email-body {
|
||||
background: white;
|
||||
border: 1px solid rgba(142, 142, 147, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.email-body iframe {
|
||||
width: 100%;
|
||||
border: none;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.email-text {
|
||||
white-space: pre-wrap;
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
font-size: clamp(0.75rem, 2vw, 0.85rem);
|
||||
line-height: 1.5;
|
||||
color: #1d1d1f;
|
||||
}
|
||||
|
||||
.no-email-message {
|
||||
text-align: center;
|
||||
color: #86868b;
|
||||
font-style: italic;
|
||||
padding: 32px;
|
||||
font-size: clamp(0.9rem, 2vw, 1rem);
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
background: rgba(142, 142, 147, 0.9);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: clamp(0.7rem, 2vw, 0.8rem);
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.refresh-btn:hover {
|
||||
background: rgba(142, 142, 147, 1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 作者区域样式 */
|
||||
.author-title {
|
||||
font-size: clamp(1.2rem, 3vw, 1.4rem);
|
||||
font-weight: 600;
|
||||
color: #1d1d1f;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.wechat-qr {
|
||||
width: clamp(150px, 25vw, 180px);
|
||||
height: clamp(150px, 25vw, 180px);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
background: white;
|
||||
}
|
||||
|
||||
.qr-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 12px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.qr-image:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.author-info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.author-info p {
|
||||
color: #515154;
|
||||
font-size: clamp(0.8rem, 2vw, 0.9rem);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.author-info .highlight {
|
||||
color: #007AFF;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 超大屏幕优化 (1440px+) */
|
||||
@media (min-width: 1440px) {
|
||||
.main-container {
|
||||
max-width: 1600px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 56px;
|
||||
}
|
||||
|
||||
.author-section {
|
||||
width: 320px;
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大屏幕优化 (1200px - 1439px) */
|
||||
@media (max-width: 1439px) and (min-width: 1200px) {
|
||||
.main-container {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.author-section {
|
||||
width: 260px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 中大屏幕 (992px - 1199px) */
|
||||
@media (max-width: 1199px) and (min-width: 992px) {
|
||||
.main-container {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.author-section {
|
||||
width: 240px;
|
||||
padding: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 中等屏幕 (768px - 991px) */
|
||||
@media (max-width: 991px) and (min-width: 768px) {
|
||||
.main-container {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 600px;
|
||||
padding: 36px;
|
||||
}
|
||||
|
||||
.author-section {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.copy-btn, .clear-btn, .view-email-btn {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏幕 (576px - 767px) */
|
||||
@media (max-width: 767px) and (min-width: 576px) {
|
||||
body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.container, .author-section {
|
||||
padding: 28px 20px;
|
||||
border-radius: 20px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.copy-btn, .clear-btn, .view-email-btn {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.email-field {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.email-field-label {
|
||||
min-width: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.email-header h3 {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超小屏幕 (< 576px) */
|
||||
@media (max-width: 575px) {
|
||||
body {
|
||||
padding: 12px;
|
||||
align-items: flex-start;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.container, .author-section {
|
||||
padding: 24px 16px;
|
||||
border-radius: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.generate-btn {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.copy-btn, .clear-btn, .view-email-btn {
|
||||
width: 100%;
|
||||
padding: 14px 20px;
|
||||
}
|
||||
|
||||
.features {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 12px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.wechat-qr {
|
||||
width: clamp(120px, 35vw, 150px);
|
||||
height: clamp(120px, 35vw, 150px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 横屏手机优化 */
|
||||
@media (max-height: 500px) and (orientation: landscape) {
|
||||
body {
|
||||
align-items: flex-start;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.author-section {
|
||||
width: 220px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.wechat-qr {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 高分辨率屏幕优化 */
|
||||
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
|
||||
.email-address, .email-text {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
}
|
||||
|
||||
/* 打印样式 */
|
||||
@media print {
|
||||
body {
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
flex-direction: column;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.container, .author-section {
|
||||
background: white;
|
||||
box-shadow: none;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.generate-btn, .copy-btn, .clear-btn, .view-email-btn, .refresh-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 无障碍支持 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 高对比度模式 */
|
||||
@media (prefers-contrast: high) {
|
||||
.container, .author-section {
|
||||
border: 2px solid #000;
|
||||
}
|
||||
|
||||
.generate-btn, .copy-btn, .clear-btn, .view-email-btn {
|
||||
border: 2px solid #000;
|
||||
}
|
||||
}
|
||||
BIN
前端/wechat-qr.jpg
Normal file
BIN
前端/wechat-qr.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
684
批量删除临时邮件/auto-cleanup-emails.html
Normal file
684
批量删除临时邮件/auto-cleanup-emails.html
Normal file
@@ -0,0 +1,684 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>自动批量删除临时邮箱</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #333;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
color: #666;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.warning h3 {
|
||||
margin-bottom: 10px;
|
||||
color: #d63031;
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
margin: 30px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 15px 30px;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: linear-gradient(45deg, #ff6b6b, #ee5a52);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: linear-gradient(45deg, #17a2b8, #138496);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
margin-top: 30px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-section.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
background: #e9ecef;
|
||||
border-radius: 10px;
|
||||
height: 25px;
|
||||
margin: 20px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 0.9rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.email-list {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.email-item {
|
||||
background: white;
|
||||
padding: 12px 15px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #667eea;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.email-item.deleting {
|
||||
opacity: 0.6;
|
||||
border-left-color: #ffa500;
|
||||
}
|
||||
|
||||
.email-item.deleted {
|
||||
background: #d4edda;
|
||||
border-left-color: #28a745;
|
||||
}
|
||||
|
||||
.email-item.failed {
|
||||
background: #f8d7da;
|
||||
border-left-color: #dc3545;
|
||||
}
|
||||
|
||||
.email-address {
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.email-status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.status-deleting {
|
||||
background: #ffeaa7;
|
||||
color: #d68910;
|
||||
}
|
||||
|
||||
.status-deleted {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-failed {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #667eea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.log-section {
|
||||
background: #2d3748;
|
||||
color: #e2e8f0;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.log-section.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.current-operation {
|
||||
background: #e3f2fd;
|
||||
border: 1px solid #2196f3;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
color: #1565c0;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.current-operation.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🧹 自动批量删除临时邮箱</h1>
|
||||
<p>一键自动获取并删除所有临时邮箱</p>
|
||||
</div>
|
||||
|
||||
<div class="warning">
|
||||
<h3>⚠️ 重要警告</h3>
|
||||
<ul>
|
||||
<li>此工具将自动获取数据库中的所有临时邮箱并删除</li>
|
||||
<li>删除操作包括:Cloudflare路由 + 邮件数据 + 邮箱记录</li>
|
||||
<li>删除操作不可撤销,请谨慎使用</li>
|
||||
<li>建议先点击"获取邮箱列表"查看将要删除的内容</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="stats-section">
|
||||
<h3>📊 删除统计</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" id="totalEmails">-</div>
|
||||
<div class="stat-label">总邮箱数</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" id="totalMessages">-</div>
|
||||
<div class="stat-label">总邮件数</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" id="deletedCount">0</div>
|
||||
<div class="stat-label">已删除</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" id="failedCount">0</div>
|
||||
<div class="stat-label">删除失败</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="current-operation" id="currentOperation">
|
||||
正在执行操作...
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="btn btn-info" onclick="loadEmailList()" id="loadBtn">📥 获取邮箱列表</button>
|
||||
<button class="btn btn-danger" onclick="confirmAndDeleteAll()" id="deleteBtn">🗑️ 一键删除全部</button>
|
||||
</div>
|
||||
|
||||
<div class="progress-section" id="progressSection">
|
||||
<h3>🔄 删除进度</h3>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill">0%</div>
|
||||
</div>
|
||||
<div class="email-list" id="emailList"></div>
|
||||
</div>
|
||||
|
||||
<div class="log-section" id="logSection">
|
||||
<div id="logContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let emailsToDelete = [];
|
||||
let isOperating = false;
|
||||
let deletedCount = 0;
|
||||
let failedCount = 0;
|
||||
|
||||
// 日志函数
|
||||
function log(message, type = 'info') {
|
||||
const logSection = document.getElementById('logSection');
|
||||
const logContent = document.getElementById('logContent');
|
||||
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const logEntry = `[${timestamp}] ${message}\n`;
|
||||
|
||||
logContent.textContent += logEntry;
|
||||
logSection.classList.add('show');
|
||||
logSection.scrollTop = logSection.scrollHeight;
|
||||
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
// 显示当前操作
|
||||
function showCurrentOperation(message) {
|
||||
const currentOperation = document.getElementById('currentOperation');
|
||||
currentOperation.textContent = message;
|
||||
currentOperation.classList.add('show');
|
||||
}
|
||||
|
||||
// 隐藏当前操作
|
||||
function hideCurrentOperation() {
|
||||
const currentOperation = document.getElementById('currentOperation');
|
||||
currentOperation.classList.remove('show');
|
||||
}
|
||||
|
||||
// 延迟函数
|
||||
function delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// 更新统计信息
|
||||
function updateStats(totalEmails = null, totalMessages = null) {
|
||||
if (totalEmails !== null) {
|
||||
document.getElementById('totalEmails').textContent = totalEmails;
|
||||
}
|
||||
if (totalMessages !== null) {
|
||||
document.getElementById('totalMessages').textContent = totalMessages;
|
||||
}
|
||||
document.getElementById('deletedCount').textContent = deletedCount;
|
||||
document.getElementById('failedCount').textContent = failedCount;
|
||||
}
|
||||
|
||||
// 更新进度条
|
||||
function updateProgress(current, total) {
|
||||
const progressSection = document.getElementById('progressSection');
|
||||
const progressFill = document.getElementById('progressFill');
|
||||
|
||||
if (total === 0) {
|
||||
progressSection.classList.remove('show');
|
||||
return;
|
||||
}
|
||||
|
||||
const percentage = Math.round((current / total) * 100);
|
||||
progressFill.style.width = `${percentage}%`;
|
||||
progressFill.textContent = `${current}/${total} (${percentage}%)`;
|
||||
|
||||
progressSection.classList.add('show');
|
||||
}
|
||||
|
||||
// 显示邮箱列表
|
||||
function displayEmailList(emails, showStatus = false) {
|
||||
const emailList = document.getElementById('emailList');
|
||||
|
||||
if (emails.length === 0) {
|
||||
emailList.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">没有找到临时邮箱</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
emailList.innerHTML = emails.map((emailData, index) => {
|
||||
const email = typeof emailData === 'string' ? emailData : emailData.email;
|
||||
const emailCount = typeof emailData === 'object' ? emailData.emailCount : 0;
|
||||
|
||||
return `
|
||||
<div class="email-item" id="item-${index}">
|
||||
<div>
|
||||
<div class="email-address">${email}</div>
|
||||
<div style="font-size: 0.8rem; color: #666; margin-top: 2px;">
|
||||
邮件数: ${emailCount} 封
|
||||
</div>
|
||||
</div>
|
||||
${showStatus ? '<div class="email-status status-pending">等待删除</div>' : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 获取邮箱列表
|
||||
async function loadEmailList() {
|
||||
if (isOperating) {
|
||||
alert('操作正在进行中,请稍候...');
|
||||
return;
|
||||
}
|
||||
|
||||
isOperating = true;
|
||||
const loadBtn = document.getElementById('loadBtn');
|
||||
loadBtn.disabled = true;
|
||||
loadBtn.innerHTML = '<span class="loading"></span>获取中...';
|
||||
|
||||
showCurrentOperation('正在从数据库获取邮箱列表...');
|
||||
log('📥 开始获取邮箱列表...');
|
||||
|
||||
try {
|
||||
const response = await fetch('云函数链接GET_all_temp_emails', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
let data;
|
||||
|
||||
try {
|
||||
data = JSON.parse(responseText);
|
||||
if (data.body && typeof data.body === 'string') {
|
||||
data = JSON.parse(data.body);
|
||||
}
|
||||
} catch (parseError) {
|
||||
throw new Error(`响应解析失败: ${responseText}`);
|
||||
}
|
||||
|
||||
if (data.success && data.data) {
|
||||
emailsToDelete = data.data.emails;
|
||||
const stats = data.data.statistics;
|
||||
|
||||
updateStats(stats.totalEmails, stats.totalEmailMessages);
|
||||
displayEmailList(emailsToDelete, false);
|
||||
|
||||
log(`✅ 成功获取 ${emailsToDelete.length} 个邮箱`);
|
||||
log(`📊 统计: 总邮箱${stats.totalEmails}个, 活跃${stats.activeEmails}个, 总邮件${stats.totalEmailMessages}封`);
|
||||
|
||||
hideCurrentOperation();
|
||||
|
||||
} else {
|
||||
throw new Error(data.error || '获取邮箱列表失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
log(`❌ 获取邮箱列表失败: ${error.message}`);
|
||||
alert('获取邮箱列表失败: ' + error.message);
|
||||
hideCurrentOperation();
|
||||
} finally {
|
||||
isOperating = false;
|
||||
loadBtn.disabled = false;
|
||||
loadBtn.textContent = '📥 获取邮箱列表';
|
||||
}
|
||||
}
|
||||
|
||||
// 确认并删除所有邮箱
|
||||
async function confirmAndDeleteAll() {
|
||||
if (isOperating) {
|
||||
alert('操作正在进行中,请稍候...');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果没有加载邮箱列表,先自动加载
|
||||
if (emailsToDelete.length === 0) {
|
||||
log('📥 邮箱列表为空,自动获取邮箱列表...');
|
||||
await loadEmailList();
|
||||
|
||||
if (emailsToDelete.length === 0) {
|
||||
alert('没有找到需要删除的邮箱');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const confirmed = confirm(`确定要删除所有 ${emailsToDelete.length} 个临时邮箱吗?\n\n此操作将:\n- 删除Cloudflare中的邮箱路由\n- 删除数据库中的邮件记录\n- 删除临时邮箱记录\n\n此操作不可撤销!`);
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await executeDeleteAll();
|
||||
}
|
||||
|
||||
// 执行删除所有邮箱
|
||||
async function executeDeleteAll() {
|
||||
isOperating = true;
|
||||
deletedCount = 0;
|
||||
failedCount = 0;
|
||||
|
||||
const deleteBtn = document.getElementById('deleteBtn');
|
||||
deleteBtn.disabled = true;
|
||||
deleteBtn.innerHTML = '<span class="loading"></span>删除中...';
|
||||
|
||||
showCurrentOperation(`正在删除 ${emailsToDelete.length} 个邮箱...`);
|
||||
log(`🚀 开始批量删除 ${emailsToDelete.length} 个邮箱...`);
|
||||
|
||||
displayEmailList(emailsToDelete, true);
|
||||
updateProgress(0, emailsToDelete.length);
|
||||
|
||||
try {
|
||||
for (let i = 0; i < emailsToDelete.length; i++) {
|
||||
const emailData = emailsToDelete[i];
|
||||
const email = typeof emailData === 'string' ? emailData : emailData.email;
|
||||
const itemElement = document.getElementById(`item-${i}`);
|
||||
const statusElement = itemElement?.querySelector('.email-status');
|
||||
|
||||
try {
|
||||
showCurrentOperation(`正在删除: ${email} (${i + 1}/${emailsToDelete.length})`);
|
||||
log(`🗑️ 正在删除: ${email} (${i + 1}/${emailsToDelete.length})`);
|
||||
|
||||
if (statusElement) {
|
||||
statusElement.textContent = '删除中...';
|
||||
statusElement.className = 'email-status status-deleting';
|
||||
}
|
||||
if (itemElement) {
|
||||
itemElement.classList.add('deleting');
|
||||
}
|
||||
|
||||
// 调用云函数删除邮箱
|
||||
const response = await fetch('云函数链接Delete_edu_cloudfare', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email: email })
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
let data;
|
||||
|
||||
try {
|
||||
data = JSON.parse(responseText);
|
||||
if (data.body && typeof data.body === 'string') {
|
||||
data = JSON.parse(data.body);
|
||||
}
|
||||
} catch (parseError) {
|
||||
throw new Error(`响应解析失败: ${responseText}`);
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
deletedCount++;
|
||||
log(`✅ 成功删除: ${email}`);
|
||||
|
||||
if (data.summary) {
|
||||
log(` - Cloudflare路由: ${data.summary.cloudflareRoutes} 个`);
|
||||
log(` - 邮件记录: ${data.summary.emailRecords} 条`);
|
||||
log(` - 邮箱记录: ${data.summary.tempEmailRecords} 条`);
|
||||
}
|
||||
|
||||
if (statusElement) {
|
||||
statusElement.textContent = '已删除';
|
||||
statusElement.className = 'email-status status-deleted';
|
||||
}
|
||||
if (itemElement) {
|
||||
itemElement.classList.remove('deleting');
|
||||
itemElement.classList.add('deleted');
|
||||
}
|
||||
} else {
|
||||
throw new Error(data.error || data.message || '删除失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
failedCount++;
|
||||
log(`❌ 删除失败: ${email} - ${error.message}`);
|
||||
|
||||
if (statusElement) {
|
||||
statusElement.textContent = '删除失败';
|
||||
statusElement.className = 'email-status status-failed';
|
||||
}
|
||||
if (itemElement) {
|
||||
itemElement.classList.remove('deleting');
|
||||
itemElement.classList.add('failed');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新统计和进度
|
||||
updateStats();
|
||||
updateProgress(i + 1, emailsToDelete.length);
|
||||
|
||||
// 添加延迟避免过于频繁的请求
|
||||
if (i < emailsToDelete.length - 1) {
|
||||
await delay(2000); // 2秒延迟
|
||||
}
|
||||
}
|
||||
|
||||
// 显示最终结果
|
||||
hideCurrentOperation();
|
||||
log('🎉 批量删除操作完成!');
|
||||
log(`📊 总计: ${emailsToDelete.length} 个邮箱`);
|
||||
log(`✅ 成功删除: ${deletedCount} 个`);
|
||||
log(`❌ 删除失败: ${failedCount} 个`);
|
||||
|
||||
alert(`删除完成!\n\n总计: ${emailsToDelete.length} 个邮箱\n成功: ${deletedCount} 个\n失败: ${failedCount} 个`);
|
||||
|
||||
} finally {
|
||||
isOperating = false;
|
||||
deleteBtn.disabled = false;
|
||||
deleteBtn.textContent = '🗑️ 一键删除全部';
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时自动获取邮箱列表
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
log('🧹 自动批量删除工具已加载');
|
||||
log('💡 点击"获取邮箱列表"查看将要删除的邮箱,或直接点击"一键删除全部"');
|
||||
updateStats();
|
||||
|
||||
// 自动获取邮箱列表
|
||||
setTimeout(() => {
|
||||
loadEmailList();
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
288
批量删除临时邮件/cleanup-all-emails.js
Normal file
288
批量删除临时邮件/cleanup-all-emails.js
Normal file
@@ -0,0 +1,288 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Cloudflare 临时邮箱清理脚本
|
||||
* 用于删除所有临时邮箱路由规则
|
||||
*/
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
// Cloudflare 配置
|
||||
const config = {
|
||||
api_token: "※※※※※※※※※※※※※※※※※※※※※※※※※※※※",
|
||||
zone_id: "※※※※※※※※※※※※※※※※※※※※※※※※※※",
|
||||
domain: "※※※※※※※※※"
|
||||
};
|
||||
|
||||
class CloudflareEmailCleaner {
|
||||
constructor() {
|
||||
this.apiToken = config.api_token;
|
||||
this.zoneId = config.zone_id;
|
||||
this.domain = config.domain;
|
||||
this.baseURL = 'https://api.cloudflare.com/client/v4';
|
||||
}
|
||||
|
||||
// 延迟函数,避免API频率限制
|
||||
async delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// 获取所有邮箱路由规则
|
||||
async getAllEmailRoutes() {
|
||||
console.log('🔍 正在获取所有邮箱路由规则...');
|
||||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${this.baseURL}/zones/${this.zoneId}/email/routing/rules`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.apiToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.data.success) {
|
||||
throw new Error(`获取路由规则失败: ${JSON.stringify(response.data.errors)}`);
|
||||
}
|
||||
|
||||
const rules = response.data.result;
|
||||
console.log(`📋 找到 ${rules.length} 个邮箱路由规则`);
|
||||
|
||||
return rules;
|
||||
} catch (error) {
|
||||
console.error('❌ 获取邮箱路由规则失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤临时邮箱规则
|
||||
filterTempEmailRoutes(rules) {
|
||||
console.log('🔍 正在筛选临时邮箱规则...');
|
||||
|
||||
const tempRules = rules.filter(rule => {
|
||||
// 检查规则名称是否以 "temp-" 开头
|
||||
const isTempByName = rule.name && rule.name.startsWith('temp-');
|
||||
|
||||
// 检查是否匹配我们的域名
|
||||
const isDomainMatch = rule.matchers && rule.matchers.some(matcher =>
|
||||
matcher.field === 'to' &&
|
||||
matcher.value &&
|
||||
matcher.value.includes(this.domain)
|
||||
);
|
||||
|
||||
// 检查是否是 Worker 类型的路由
|
||||
const isWorkerRoute = rule.actions && rule.actions.some(action =>
|
||||
action.type === 'worker'
|
||||
);
|
||||
|
||||
return isTempByName || (isDomainMatch && isWorkerRoute);
|
||||
});
|
||||
|
||||
console.log(`📝 筛选出 ${tempRules.length} 个临时邮箱规则`);
|
||||
|
||||
// 显示详细信息
|
||||
tempRules.forEach((rule, index) => {
|
||||
const email = rule.matchers?.[0]?.value || '未知邮箱';
|
||||
const workerName = rule.actions?.[0]?.value?.[0] || '未知Worker';
|
||||
console.log(` ${index + 1}. ${rule.name} - ${email} -> ${workerName}`);
|
||||
});
|
||||
|
||||
return tempRules;
|
||||
}
|
||||
|
||||
// 删除单个路由规则
|
||||
async deleteRoute(rule) {
|
||||
const email = rule.matchers?.[0]?.value || '未知邮箱';
|
||||
|
||||
try {
|
||||
console.log(`🗑️ 正在删除: ${rule.name} (${email})`);
|
||||
|
||||
const response = await axios.delete(
|
||||
`${this.baseURL}/zones/${this.zoneId}/email/routing/rules/${rule.id}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.apiToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.data.success) {
|
||||
throw new Error(`删除失败: ${JSON.stringify(response.data.errors)}`);
|
||||
}
|
||||
|
||||
console.log(`✅ 成功删除: ${rule.name} (${email})`);
|
||||
return { success: true, rule: rule };
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 删除失败: ${rule.name} (${email}) - ${error.message}`);
|
||||
return { success: false, rule: rule, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 批量删除所有临时邮箱路由
|
||||
async deleteAllTempRoutes(dryRun = false) {
|
||||
console.log('🚀 开始清理所有临时邮箱路由...');
|
||||
console.log(`📍 域名: ${this.domain}`);
|
||||
console.log(`📍 Zone ID: ${this.zoneId}`);
|
||||
|
||||
if (dryRun) {
|
||||
console.log('🔍 这是预览模式,不会实际删除任何内容');
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 获取所有路由规则
|
||||
const allRoutes = await this.getAllEmailRoutes();
|
||||
|
||||
if (allRoutes.length === 0) {
|
||||
console.log('✨ 没有找到任何邮箱路由规则');
|
||||
return { total: 0, deleted: 0, failed: 0 };
|
||||
}
|
||||
|
||||
// 2. 筛选临时邮箱规则
|
||||
const tempRoutes = this.filterTempEmailRoutes(allRoutes);
|
||||
|
||||
if (tempRoutes.length === 0) {
|
||||
console.log('✨ 没有找到任何临时邮箱规则');
|
||||
return { total: 0, deleted: 0, failed: 0 };
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
console.log(`\n📋 预览模式完成,找到 ${tempRoutes.length} 个临时邮箱规则`);
|
||||
console.log('💡 运行 node cleanup-all-emails.js --delete 来实际删除');
|
||||
return { total: tempRoutes.length, deleted: 0, failed: 0 };
|
||||
}
|
||||
|
||||
// 3. 确认删除
|
||||
console.log(`\n⚠️ 即将删除 ${tempRoutes.length} 个临时邮箱规则`);
|
||||
console.log('⚠️ 此操作不可撤销!');
|
||||
|
||||
// 在Node.js环境中,我们跳过交互式确认
|
||||
console.log('🔄 开始批量删除...');
|
||||
|
||||
// 4. 批量删除
|
||||
const results = [];
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
for (let i = 0; i < tempRoutes.length; i++) {
|
||||
const rule = tempRoutes[i];
|
||||
|
||||
// 添加延迟避免API频率限制
|
||||
if (i > 0) {
|
||||
console.log('⏳ 等待2秒避免API频率限制...');
|
||||
await this.delay(2000);
|
||||
}
|
||||
|
||||
const result = await this.deleteRoute(rule);
|
||||
results.push(result);
|
||||
|
||||
if (result.success) {
|
||||
successCount++;
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
|
||||
// 显示进度
|
||||
console.log(`📊 进度: ${i + 1}/${tempRoutes.length} (成功: ${successCount}, 失败: ${failCount})`);
|
||||
}
|
||||
|
||||
// 5. 显示最终结果
|
||||
console.log('\n🎉 清理完成!');
|
||||
console.log(`📊 总计: ${tempRoutes.length} 个规则`);
|
||||
console.log(`✅ 成功删除: ${successCount} 个`);
|
||||
console.log(`❌ 删除失败: ${failCount} 个`);
|
||||
|
||||
if (failCount > 0) {
|
||||
console.log('\n❌ 失败的规则:');
|
||||
results.filter(r => !r.success).forEach((result, index) => {
|
||||
const email = result.rule.matchers?.[0]?.value || '未知邮箱';
|
||||
console.log(` ${index + 1}. ${result.rule.name} (${email}) - ${result.error}`);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
total: tempRoutes.length,
|
||||
deleted: successCount,
|
||||
failed: failCount,
|
||||
results: results
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('💥 清理过程中发生错误:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示统计信息
|
||||
async showStats() {
|
||||
console.log('📊 正在获取邮箱路由统计信息...');
|
||||
|
||||
try {
|
||||
const allRoutes = await this.getAllEmailRoutes();
|
||||
const tempRoutes = this.filterTempEmailRoutes(allRoutes);
|
||||
|
||||
console.log('\n📈 统计信息:');
|
||||
console.log(`📋 总路由规则数: ${allRoutes.length}`);
|
||||
console.log(`🏷️ 临时邮箱规则数: ${tempRoutes.length}`);
|
||||
console.log(`🌐 域名: ${this.domain}`);
|
||||
|
||||
if (tempRoutes.length > 0) {
|
||||
console.log('\n📝 临时邮箱详情:');
|
||||
tempRoutes.forEach((rule, index) => {
|
||||
const email = rule.matchers?.[0]?.value || '未知邮箱';
|
||||
const createdDate = new Date(rule.created_on || Date.now()).toLocaleString();
|
||||
console.log(` ${index + 1}. ${email} (创建于: ${createdDate})`);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 获取统计信息失败:', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const cleaner = new CloudflareEmailCleaner();
|
||||
|
||||
console.log('🧹 Cloudflare 临时邮箱清理工具');
|
||||
console.log('=====================================\n');
|
||||
|
||||
try {
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
console.log('使用方法:');
|
||||
console.log(' node cleanup-all-emails.js # 预览模式,显示将要删除的规则');
|
||||
console.log(' node cleanup-all-emails.js --delete # 实际删除所有临时邮箱规则');
|
||||
console.log(' node cleanup-all-emails.js --stats # 显示统计信息');
|
||||
console.log(' node cleanup-all-emails.js --help # 显示帮助信息');
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.includes('--stats')) {
|
||||
await cleaner.showStats();
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldDelete = args.includes('--delete');
|
||||
const result = await cleaner.deleteAllTempRoutes(!shouldDelete);
|
||||
|
||||
if (!shouldDelete) {
|
||||
console.log('\n💡 这只是预览,没有实际删除任何内容');
|
||||
console.log('💡 运行 node cleanup-all-emails.js --delete 来实际删除');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n💥 脚本执行失败:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = CloudflareEmailCleaner;
|
||||
Reference in New Issue
Block a user