Add files via upload

可以部署到云端的临时邮箱:workers+unicloud;不需要额外的转发邮箱;你可以部署到你的网站、小程序、app上。
This commit is contained in:
wojiadexiaoming-copy
2025-08-10 18:48:13 +08:00
committed by GitHub
parent d800cdd503
commit de17ce7fdc
17 changed files with 4427 additions and 0 deletions

View 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 '编码错误';
}
}
};

View 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);
}
};

View File

@@ -0,0 +1,9 @@
{
"name": "Delete_edu_cloudfare",
"version": "1.0.0",
"description": "删除临时邮箱及相关数据",
"main": "index.js",
"dependencies": {
"axios": "^1.5.0"
}
}

View 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);
}
};

View File

@@ -0,0 +1,9 @@
{
"name": "GET_all_temp_emails",
"version": "1.0.0",
"description": "从Cloudflare获取所有临时邮箱记录",
"main": "index.js",
"dependencies": {
"axios": "^1.5.0"
}
}

View 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);
}
};

View File

@@ -0,0 +1,7 @@
{
"name": "GET_cloudflare_edukg_email",
"version": "1.0.0",
"description": "获取cloudflare_edukg_email数据集中的最新邮件",
"main": "index.js",
"dependencies": {}
}

View 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);
}
};

View 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);
}
};

View File

@@ -0,0 +1,9 @@
{
"name": "generate-email",
"version": "1.0.0",
"description": "生成临时邮箱云函数",
"main": "index.js",
"dependencies": {
"axios": "^1.5.0"
}
}

View 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
View 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
View 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, '&quot;')}" 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View 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>

View 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;