feat(docker): 安装 tzdata 并设置 TZ=Asia/Shanghai 以让 Node.js 使用上海时区
This commit is contained in:
parent
bb25928a8e
commit
77030cc8fc
|
|
@ -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 . .
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,11 +18,23 @@ 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}`;
|
||||
})
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ services:
|
|||
image: mysql:8.0
|
||||
container_name: email-mysql
|
||||
restart: always
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
env_file:
|
||||
- compose.env
|
||||
volumes:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue