diff --git a/backend/Dockerfile b/backend/Dockerfile index 420b0f2..406ea70 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,14 +1,13 @@ FROM node:20-alpine +# Set timezone to Asia/Shanghai FIRST +# This ensures that any native modules compiled during npm install are linked correctly. +RUN apk add --no-cache tzdata +ENV TZ=Asia/Shanghai + WORKDIR /usr/src/app COPY package*.json ./ - -# Set timezone to Asia/Shanghai -RUN apk add --no-cache tzdata -ENV TZ=Asia/Shanghai -ENV NODE_ENV=production - RUN npm install COPY . . @@ -23,4 +22,4 @@ EXPOSE 25 # ENV DB_PASSWORD=... # ENV DB_NAME=... -CMD [ "node", "app.js" ] +CMD [ "node", "app.js" ] \ No newline at end of file diff --git a/backend/init.sql b/backend/init.sql index 94c90a3..e91caa4 100644 --- a/backend/init.sql +++ b/backend/init.sql @@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS emails ( sender VARCHAR(255) NOT NULL, subject VARCHAR(512), body TEXT, - received_at DATETIME DEFAULT CURRENT_TIMESTAMP, + received_at DATETIME, raw MEDIUMBLOB ); @@ -15,4 +15,4 @@ CREATE TABLE IF NOT EXISTS email_attachments ( content_type VARCHAR(128), content LONGBLOB, FOREIGN KEY (email_id) REFERENCES emails(id) ON DELETE CASCADE -); +); \ No newline at end of file diff --git a/backend/logger.js b/backend/logger.js index 03f6241..80319b9 100644 --- a/backend/logger.js +++ b/backend/logger.js @@ -4,7 +4,8 @@ const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp({ - format: 'YYYY-MM-DD HH:mm:ss' + format: 'YYYY-MM-DD HH:mm:ss', + tz: 'Asia/Shanghai' }), winston.format.errors({ stack: true }), winston.format.splat(), @@ -17,13 +18,25 @@ const logger = winston.createLogger({ ] }); +// 在非生产环境下,添加一个带有着色的控制台输出 if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), - winston.format.simple() + // 确保控制台日志也有正确格式和时区的时间戳 + winston.format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss', + tz: 'Asia/Shanghai' + }), + winston.format.printf(({ level, message, timestamp, stack }) => { + if (stack) { + // 打印错误堆栈 + return `${timestamp} ${level}: ${message}\n${stack}`; + } + return `${timestamp} ${level}: ${message}`; + }) ) })); } -module.exports = logger; +module.exports = logger; \ No newline at end of file diff --git a/backend/saveEmail.js b/backend/saveEmail.js index 7e00f4b..9745343 100644 --- a/backend/saveEmail.js +++ b/backend/saveEmail.js @@ -1,6 +1,7 @@ const { simpleParser } = require('mailparser'); const db = require('./db'); const emitter = require('./eventEmitter'); +const logger = require('./logger'); // Helper function to convert stream to buffer function streamToBuffer(stream) { @@ -26,13 +27,18 @@ async function saveEmail(stream) { const subject = parsed.subject || 'No Subject'; const body = parsed.text || (parsed.html || ''); + // Manually create a timestamp for 'Asia/Shanghai' timezone + const received_at = new Date().toLocaleString('sv-SE', { + timeZone: 'Asia/Shanghai' + }); + const [result] = await db.execute( - 'INSERT INTO emails (recipient, sender, subject, body, raw) VALUES (?, ?, ?, ?, ?)', - [recipient, sender, subject, body, rawEmail] + 'INSERT INTO emails (recipient, sender, subject, body, raw, received_at) VALUES (?, ?, ?, ?, ?, ?)', + [recipient, sender, subject, body, rawEmail, received_at] ); const newEmailId = result.insertId; - console.log(`Email from <${sender}> to <${recipient}> saved with ID: ${newEmailId}`); + logger.info(`Email from <${sender}> to <${recipient}> saved with ID: ${newEmailId}`); if (parsed.attachments && parsed.attachments.length > 0) { for (const attachment of parsed.attachments) { @@ -40,7 +46,7 @@ async function saveEmail(stream) { 'INSERT INTO email_attachments (email_id, filename, content_type, content) VALUES (?, ?, ?, ?)', [newEmailId, attachment.filename, attachment.contentType, attachment.content] ); - console.log(`Attachment ${attachment.filename} saved.`); + logger.info(`Attachment ${attachment.filename} saved for email ID: ${newEmailId}`); } } @@ -48,15 +54,17 @@ async function saveEmail(stream) { const [rows] = await db.execute('SELECT id, sender, recipient, subject, body, received_at FROM emails WHERE id = ?', [newEmailId]); if (rows.length > 0) { emitter.emit('newEmail', rows[0]); + logger.info(`Event 'newEmail' emitted for email ID: ${newEmailId}`); } } catch (error) { - console.error('Failed to save email:', error); - // We should not exit the process here, but maybe throw the error - // so the caller (SMTPServer) can handle it. + logger.error('Failed to save email:', { + errorMessage: error.message, + errorStack: error.stack + }); + // Re-throw the error so the caller (SMTPServer) can handle it. throw error; } } -module.exports = { saveEmail }; - +module.exports = { saveEmail }; \ No newline at end of file diff --git a/docker-compose.build.yml b/docker-compose.build.yml index ddfd631..726dbab 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -17,6 +17,8 @@ services: image: mysql:8.0 container_name: email-mysql restart: always + environment: + - TZ=Asia/Shanghai env_file: - compose.env volumes: diff --git a/docker-compose.full.yml b/docker-compose.full.yml index 718bd4b..1ce9a09 100644 --- a/docker-compose.full.yml +++ b/docker-compose.full.yml @@ -17,6 +17,8 @@ services: image: registry.cn-hangzhou.aliyuncs.com/pull-image/mysql:8.0 container_name: email-mysql restart: always + environment: + - TZ=Asia/Shanghai env_file: - compose.full.env volumes: diff --git a/docker-compose.yml b/docker-compose.yml index da9bf4c..1c3aadd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,8 @@ services: image: registry.cn-hangzhou.aliyuncs.com/pull-image/email-unlimit-backend:latest container_name: email-backend restart: always + environment: + - TZ=Asia/Shanghai ports: - "5182:5182" # API port - "25:25" # SMTP port