172 lines
5.6 KiB
JavaScript
172 lines
5.6 KiB
JavaScript
const express = require('express');
|
||
const bodyParser = require('body-parser');
|
||
const cors = require('cors');
|
||
const nodemailer = require('nodemailer');
|
||
const dotenv = require('dotenv');
|
||
|
||
// 加载环境变量
|
||
dotenv.config();
|
||
|
||
const app = express();
|
||
const port = process.env.PORT || 3001;
|
||
|
||
// 中间件
|
||
app.use(cors({
|
||
origin: '*', // 允许所有来源访问
|
||
methods: ['GET', 'POST', 'OPTIONS'], // 允许的HTTP方法
|
||
allowedHeaders: ['Content-Type', 'Authorization'], // 允许的请求头
|
||
credentials: true // 允许发送凭证
|
||
}));
|
||
app.use(bodyParser.json());
|
||
|
||
// 处理OPTIONS预检请求
|
||
app.options('*', (req, res) => {
|
||
res.header('Access-Control-Allow-Origin', '*');
|
||
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||
res.header('Access-Control-Max-Age', '86400'); // 24小时
|
||
res.sendStatus(204);
|
||
});
|
||
|
||
// 创建Nodemailer传输器
|
||
const transporter = nodemailer.createTransport({
|
||
service: process.env.EMAIL_SERVICE || 'smtp', // 邮件服务商,例如:'gmail', 'qq', '163'等
|
||
host: process.env.EMAIL_HOST || 'smtp.163.com', // SMTP服务器
|
||
port: parseInt(process.env.EMAIL_PORT || '587'),
|
||
secure: process.env.EMAIL_SECURE === 'true', // true for 465, false for other ports
|
||
auth: {
|
||
user: process.env.EMAIL_USER || '', // 发送方邮箱
|
||
pass: process.env.EMAIL_PASS || '', // 授权码或密码
|
||
},
|
||
// 增加连接超时设置
|
||
connectionTimeout: 10000, // 10秒连接超时
|
||
greetingTimeout: 10000, // 10秒问候超时
|
||
socketTimeout: 15000, // 15秒socket超时
|
||
// 增加重试选项
|
||
pool: true, // 使用连接池
|
||
maxConnections: 5, // 最大连接数
|
||
maxMessages: 100, // 每个连接最大消息数
|
||
// 调试选项
|
||
debug: true, // 启用调试日志
|
||
});
|
||
|
||
// 验证传输器连接
|
||
transporter.verify(function(error, success) {
|
||
if (error) {
|
||
console.error('SMTP连接验证失败:', error);
|
||
} else {
|
||
console.log('SMTP服务器连接成功,准备发送邮件');
|
||
}
|
||
});
|
||
|
||
// 存储IP地址最后发送邮件的时间
|
||
const lastEmailSentTime = new Map();
|
||
|
||
// 速率限制中间件 - 60秒内只能发送一封邮件
|
||
const rateLimitMiddleware = (req, res, next) => {
|
||
const ip = req.ip || req.connection.remoteAddress;
|
||
const currentTime = Date.now();
|
||
const lastTime = lastEmailSentTime.get(ip) || 0;
|
||
const timeDiff = currentTime - lastTime;
|
||
|
||
// 如果距离上次发送不足60秒,则拒绝请求
|
||
if (lastTime && timeDiff < 60000) {
|
||
const remainingTime = Math.ceil((60000 - timeDiff) / 1000);
|
||
return res.status(429).json({
|
||
error: `请求过于频繁,请在${remainingTime}秒后再试`,
|
||
remainingTime
|
||
});
|
||
}
|
||
|
||
// 更新最后发送时间并继续处理请求
|
||
lastEmailSentTime.set(ip, currentTime);
|
||
next();
|
||
};
|
||
|
||
// 邮件发送接口
|
||
app.post('/api/send-email', rateLimitMiddleware, async (req, res) => {
|
||
const { name, email, subject, message } = req.body;
|
||
|
||
if (!name || !email || !message) {
|
||
return res.status(400).json({ error: '请填写所有必填字段' });
|
||
}
|
||
|
||
// 邮件选项
|
||
const mailOptions = {
|
||
from: `"个人网站联系表单" <${process.env.EMAIL_USER}>`,
|
||
to: process.env.EMAIL_RECIPIENT || process.env.EMAIL_USER, // 接收方邮箱
|
||
subject: subject ? `新的联系表单消息: ${subject}` : '新的联系表单消息',
|
||
html: `
|
||
<h3>您有一条新的联系表单消息</h3>
|
||
<p><strong>姓名:</strong> ${name}</p>
|
||
<p><strong>邮箱:</strong> ${email}</p>
|
||
<p><strong>主题:</strong> ${subject || '无'}</p>
|
||
<p><strong>消息:</strong></p>
|
||
<p>${message.replace(/\n/g, '<br>')}</p>
|
||
`,
|
||
// 回复选项,回复时会发送到表单提交者的邮箱
|
||
replyTo: email
|
||
};
|
||
|
||
// 最大重试次数
|
||
const maxRetries = 3;
|
||
let retries = 0;
|
||
let lastError = null;
|
||
|
||
// 重试发送邮件的函数
|
||
const attemptSendMail = async () => {
|
||
try {
|
||
const info = await transporter.sendMail(mailOptions);
|
||
console.log('邮件发送成功:', info.messageId);
|
||
return true;
|
||
} catch (error) {
|
||
lastError = error;
|
||
console.error(`发送邮件失败 (尝试 ${retries + 1}/${maxRetries}):`, error);
|
||
return false;
|
||
}
|
||
};
|
||
|
||
try {
|
||
// 尝试发送邮件,最多重试maxRetries次
|
||
let success = false;
|
||
while (retries < maxRetries && !success) {
|
||
success = await attemptSendMail();
|
||
if (!success) {
|
||
retries++;
|
||
if (retries < maxRetries) {
|
||
// 等待一段时间后重试,每次等待时间增加
|
||
const waitTime = 1000 * retries; // 1秒, 2秒, 3秒...
|
||
console.log(`等待 ${waitTime}ms 后重试...`);
|
||
await new Promise(resolve => setTimeout(resolve, waitTime));
|
||
}
|
||
}
|
||
}
|
||
|
||
if (success) {
|
||
return res.status(200).json({ success: true, message: '邮件已成功发送' });
|
||
} else {
|
||
// 所有重试都失败了
|
||
console.error('所有重试都失败,最后一个错误:', lastError);
|
||
return res.status(500).json({
|
||
error: '发送邮件失败,请稍后再试',
|
||
details: lastError ? lastError.message : '未知错误'
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('发送邮件过程中出现异常:', error);
|
||
return res.status(500).json({
|
||
error: '发送邮件过程中出现异常',
|
||
details: error.message
|
||
});
|
||
}
|
||
});
|
||
|
||
// 测试路由
|
||
app.get('/api/test', (req, res) => {
|
||
res.json({ message: 'API服务器运行正常' });
|
||
});
|
||
|
||
// 启动服务器
|
||
app.listen(port, () => {
|
||
console.log(`邮件服务API正在端口 ${port} 上运行`);
|
||
});
|