personal/api/server.js

172 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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} 上运行`);
});