Files
MC_Report/backend/mailer.js
canglan a11df22600 feat: system logs - email log + system log with admin UI
- db: email_logs (sent/failed) + system_logs (info/warn/error) tables,
  auto-migrated for existing installs
- backend/logger.js: logEmail/logSystem with try/catch (never breaks flow)
- mailer: log send result (SMTP unconfigured/sent/failed + error msg)
- webhook: log blocked-internal, non-2xx response, send failure
- server: error middleware records 500 errors to system_logs
- routes/logs.js: GET /logs/emails + /logs/system (admin/owner, filters)
- UI: 系统日志 page (owner/admin) with system/email tabs + filters
- API docs updated
2026-08-17 05:14:41 +08:00

78 lines
2.9 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 nodemailer = require('nodemailer');
const { getPool, query, getRow, getConfig } = require('./db');
const { logEmail, logSystem } = require('./logger');
async function getSmtpConfig() {
try {
const rows = await query("SELECT k, v FROM settings WHERE k LIKE 'smtp_%'");
const cfg = {};
for (const r of rows) cfg[r.k] = r.v;
return {
host: cfg.smtp_host || '',
port: parseInt(cfg.smtp_port) || 587,
secure: cfg.smtp_secure === '1',
user: cfg.smtp_user || '',
pass: cfg.smtp_pass || '',
from: cfg.smtp_from || 'noreply@example.com',
};
} catch { return { host: '', port: 587, secure: false, user: '', pass: '', from: '' }; }
}
async function getSiteName() {
const row = await getRow("SELECT v FROM settings WHERE k = 'site_name'");
return row ? row.v : 'MC举报系统';
}
async function getSiteUrl() {
const row = await getRow("SELECT v FROM settings WHERE k = 'site_url'");
return row ? row.v : 'http://localhost:3100';
}
async function getTemplate(code) {
return getRow('SELECT * FROM email_templates WHERE code = ?', [code]);
}
function renderTemplate(template, vars) {
let { subject, body } = template;
for (const [k, v] of Object.entries(vars)) {
const re = new RegExp(`\\{\\{${k}\\}\\}`, 'g');
const val = String(v ?? '');
subject = subject.replace(re, () => val);
body = body.replace(re, () => val);
}
return { subject, body };
}
let _transporter = null;
let _transporterKey = '';
function getTransporter(cfg) {
const key = `${cfg.host}:${cfg.port}:${cfg.user}`;
if (_transporter && _transporterKey === key) return _transporter;
_transporterKey = key;
_transporter = nodemailer.createTransport({ host: cfg.host, port: cfg.port, secure: cfg.secure, auth: { user: cfg.user, pass: cfg.pass } });
return _transporter;
}
async function sendEmail(to, templateCode, vars) {
try {
const cfg = await getSmtpConfig();
if (!cfg.host || !cfg.user) { console.log('[Mailer] SMTP未配置'); await logSystem('warn', 'mailer', 'SMTP未配置邮件未发送', { to, template: templateCode }); return false; }
const template = await getTemplate(templateCode);
if (!template) { console.log('[Mailer] 模板不存在:', templateCode); await logEmail(to, templateCode, '', 'failed', '模板不存在: ' + templateCode); return false; }
const siteName = await getSiteName();
const allVars = { ...vars, site_name: siteName, site_url: await getSiteUrl() };
const { subject, body } = renderTemplate(template, allVars);
const transporter = getTransporter(cfg);
await transporter.sendMail({ from: `"${siteName}" <${cfg.from}>`, to, subject, html: body });
await logEmail(to, templateCode, subject, 'sent');
return true;
} catch (err) {
console.error('[Mailer]', err.message);
await logEmail(to, templateCode, '', 'failed', err.message);
return false;
}
}
module.exports = { sendEmail, renderTemplate, getTemplate, getSmtpConfig, getSiteName, getSiteUrl };