From b728ae08e43dd5e28c8e1b4a5a695994a10e3fa2 Mon Sep 17 00:00:00 2001 From: lizhilun Date: Sun, 12 Jul 2026 17:50:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=89=E5=85=A8=E4=B8=8E?= =?UTF-8?q?=E5=A4=87=E4=BB=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backup.sh | 23 ++++++++++++++++++++++- ecosystem.config.js | 4 +++- public/js/edit-modal.js | 32 ++++++++++++++++---------------- server/app.js | 2 ++ server/config.js | 3 ++- server/routes/io.js | 27 ++++++++++++++++++--------- server/utils/logger.js | 4 ++-- 7 files changed, 65 insertions(+), 30 deletions(-) diff --git a/backup.sh b/backup.sh index 833c45a..0c8b520 100755 --- a/backup.sh +++ b/backup.sh @@ -14,7 +14,28 @@ mkdir -p "${BACKUP_DIR}" # 备份数据库 echo "[$(date '+%Y-%m-%d %H:%M:%S')] 开始备份数据库..." -cp "${SOURCE_DIR}/data.db" "${BACKUP_DIR}/data.db" +export SOURCE_DIR BACKUP_DIR +node <<'NODE' +const path = require('path'); + +const sourceDir = process.env.SOURCE_DIR; +const backupDir = process.env.BACKUP_DIR; +const Database = require(path.join(sourceDir, 'node_modules', 'better-sqlite3')); + +async function main() { + const db = new Database(path.join(sourceDir, 'data.db'), { readonly: true }); + try { + await db.backup(path.join(backupDir, 'data.db')); + } finally { + db.close(); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); +NODE if [ $? -eq 0 ]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] 数据库备份完成" else diff --git a/ecosystem.config.js b/ecosystem.config.js index e37f01f..677f8b3 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -26,7 +26,9 @@ module.exports = { env: { ...runtimeEnv, NODE_ENV: 'production', - PORT: 3000 + PORT: 3000, + HOST: runtimeEnv.HOST || '0.0.0.0', + TRUST_PROXY: runtimeEnv.TRUST_PROXY || 'loopback' }, instances: 1, autorestart: true, diff --git a/public/js/edit-modal.js b/public/js/edit-modal.js index 958dba5..7838428 100644 --- a/public/js/edit-modal.js +++ b/public/js/edit-modal.js @@ -12,46 +12,46 @@ function openEditModal(record) { if (cat === '接单' || cat === '派单') { document.getElementById('editModalTitle').textContent = cat === '派单' ? '编辑派单记录' : '编辑接单记录'; let fields = ` -
-
+
+
`; if (cat === '派单') { const rebateVal = record.rebate || (record.quantity ? (record.amount / record.quantity) : ''); - fields += `
`; + fields += `
`; } fields += ` -
-
-
+
+
+
`; body.innerHTML = fields; } else if (cat === '红包收入' || cat === '奶茶') { document.getElementById('editModalTitle').textContent = '编辑' + cat + '记录'; body.innerHTML = ` -
-
-
+
+
+
`; } else if (cat === '红包支出' || cat === '比心' || cat === '其他支出') { document.getElementById('editModalTitle').textContent = '编辑' + cat + '记录'; let fields = ` -
+
`; if (cat !== '比心') { - fields += `
`; + fields += `
`; } else { - fields += `
`; + fields += `
`; } if (cat === '其他支出') { - fields += `
`; + fields += `
`; } - fields += `
`; + fields += `
`; body.innerHTML = fields; } else { document.getElementById('editModalTitle').textContent = '编辑记录'; body.innerHTML = ` -
-
+
+
`; } // 绑定自动补全 diff --git a/server/app.js b/server/app.js index d3ba575..51c4326 100644 --- a/server/app.js +++ b/server/app.js @@ -14,6 +14,8 @@ const ioRouter = require('./routes/io'); const app = express(); +app.set('trust proxy', config.trustProxy); + // 请求日志中间件 app.use(logger.requestLogger); diff --git a/server/config.js b/server/config.js index 01cb9aa..0da96d1 100644 --- a/server/config.js +++ b/server/config.js @@ -11,7 +11,8 @@ if (isProduction && !jwtSecret) { module.exports = { // 服务器配置 port: process.env.PORT || 3000, - host: process.env.HOST || '0.0.0.0', + host: process.env.HOST || (isProduction ? '127.0.0.1' : '0.0.0.0'), + trustProxy: process.env.TRUST_PROXY || (isProduction ? 'loopback' : false), // JWT 配置 jwtSecret: jwtSecret || 'dev-only-gamer-order-secret', diff --git a/server/routes/io.js b/server/routes/io.js index 3de605e..1bb678c 100644 --- a/server/routes/io.js +++ b/server/routes/io.js @@ -21,6 +21,23 @@ function nullableDbValue(value) { return value === undefined || value === null || value === '' ? null : value; } +const csvTextFields = new Set(['type', 'category', 'boss', 'partner', 'source', 'destination', 'note', 'created_at']); + +function escapeCsvFormula(value) { + return /^[\t\r=+\-@]/.test(value) || /^[ \t]+[=+\-@]/.test(value) + ? `'${value}` + : value; +} + +function encodeCsvCell(value, col) { + if (value === null || value === undefined) return ''; + const str = csvTextFields.has(col) ? escapeCsvFormula(String(value)) : String(value); + if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')) { + return '"' + str.replace(/"/g, '""') + '"'; + } + return str; +} + // 导出数据 router.get('/export', (req, res, next) => { try { @@ -54,15 +71,7 @@ router.get('/export', (req, res, next) => { const BOM = '\uFEFF'; const cols = ['type', 'category', 'amount', 'quantity', 'unit_price', 'rebate', 'boss', 'partner', 'source', 'destination', 'note', 'created_at']; const csvRows = records.map(r => { - return cols.map(col => { - const val = r[col]; - if (val === null || val === undefined) return ''; - const str = String(val); - if (str.includes(',') || str.includes('"') || str.includes('\n')) { - return '"' + str.replace(/"/g, '""') + '"'; - } - return str; - }).join(','); + return cols.map(col => encodeCsvCell(r[col], col)).join(','); }); logger.info(`用户 ${req.user.username} 导出 CSV 数据: ${records.length} 条`); res.setHeader('Content-Type', 'text/csv; charset=utf-8'); diff --git a/server/utils/logger.js b/server/utils/logger.js index 9848d3c..82c5c6d 100644 --- a/server/utils/logger.js +++ b/server/utils/logger.js @@ -70,10 +70,10 @@ const logger = { const duration = Date.now() - start; const status = res.statusCode; const level = status >= 400 ? 'warn' : 'info'; - logger[level](`${req.method} ${req.path} ${status} ${duration}ms`); + logger[level](`${req.method} ${req.originalUrl || req.url} ${status} ${duration}ms`); }); next(); } }; -module.exports = logger; \ No newline at end of file +module.exports = logger;