修复安全与备份问题
This commit is contained in:
@@ -14,7 +14,28 @@ mkdir -p "${BACKUP_DIR}"
|
|||||||
|
|
||||||
# 备份数据库
|
# 备份数据库
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 开始备份数据库..."
|
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
|
if [ $? -eq 0 ]; then
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 数据库备份完成"
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 数据库备份完成"
|
||||||
else
|
else
|
||||||
|
|||||||
+3
-1
@@ -26,7 +26,9 @@ module.exports = {
|
|||||||
env: {
|
env: {
|
||||||
...runtimeEnv,
|
...runtimeEnv,
|
||||||
NODE_ENV: 'production',
|
NODE_ENV: 'production',
|
||||||
PORT: 3000
|
PORT: 3000,
|
||||||
|
HOST: runtimeEnv.HOST || '0.0.0.0',
|
||||||
|
TRUST_PROXY: runtimeEnv.TRUST_PROXY || 'loopback'
|
||||||
},
|
},
|
||||||
instances: 1,
|
instances: 1,
|
||||||
autorestart: true,
|
autorestart: true,
|
||||||
|
|||||||
+16
-16
@@ -12,46 +12,46 @@ function openEditModal(record) {
|
|||||||
if (cat === '接单' || cat === '派单') {
|
if (cat === '接单' || cat === '派单') {
|
||||||
document.getElementById('editModalTitle').textContent = cat === '派单' ? '编辑派单记录' : '编辑接单记录';
|
document.getElementById('editModalTitle').textContent = cat === '派单' ? '编辑派单记录' : '编辑接单记录';
|
||||||
let fields = `
|
let fields = `
|
||||||
<div class="form-group"><label>单数</label><input type="number" id="editQuantity" value="${record.quantity || ''}"></div>
|
<div class="form-group"><label>单数</label><input type="number" id="editQuantity" value="${escapeAttr(record.quantity ?? '')}"></div>
|
||||||
<div class="form-group"><label>单价(元)</label><input type="number" step="0.01" id="editPrice" value="${record.unit_price || ''}"></div>
|
<div class="form-group"><label>单价(元)</label><input type="number" step="0.01" id="editPrice" value="${escapeAttr(record.unit_price ?? '')}"></div>
|
||||||
`;
|
`;
|
||||||
if (cat === '派单') {
|
if (cat === '派单') {
|
||||||
const rebateVal = record.rebate || (record.quantity ? (record.amount / record.quantity) : '');
|
const rebateVal = record.rebate || (record.quantity ? (record.amount / record.quantity) : '');
|
||||||
fields += `<div class="form-group"><label>返点(元)</label><input type="number" step="0.01" id="editRebate" value="${rebateVal}"></div>`;
|
fields += `<div class="form-group"><label>返点(元)</label><input type="number" step="0.01" id="editRebate" value="${escapeAttr(rebateVal)}"></div>`;
|
||||||
}
|
}
|
||||||
fields += `
|
fields += `
|
||||||
<div class="form-group"><label>老板</label><input type="text" id="editBoss" value="${record.boss || ''}"></div>
|
<div class="form-group"><label>老板</label><input type="text" id="editBoss" value="${escapeAttr(record.boss || '')}"></div>
|
||||||
<div class="form-group"><label>${cat === '派单' ? '负责打单的陪玩' : '一起的陪玩'}</label><input type="text" id="editPartner" value="${record.partner || ''}"></div>
|
<div class="form-group"><label>${cat === '派单' ? '负责打单的陪玩' : '一起的陪玩'}</label><input type="text" id="editPartner" value="${escapeAttr(record.partner || '')}"></div>
|
||||||
<div class="form-group"><label>时间</label><input type="datetime-local" id="editTime" value="${localTime}"></div>
|
<div class="form-group"><label>时间</label><input type="datetime-local" id="editTime" value="${escapeAttr(localTime)}"></div>
|
||||||
`;
|
`;
|
||||||
body.innerHTML = fields;
|
body.innerHTML = fields;
|
||||||
} else if (cat === '红包收入' || cat === '奶茶') {
|
} else if (cat === '红包收入' || cat === '奶茶') {
|
||||||
document.getElementById('editModalTitle').textContent = '编辑' + cat + '记录';
|
document.getElementById('editModalTitle').textContent = '编辑' + cat + '记录';
|
||||||
body.innerHTML = `
|
body.innerHTML = `
|
||||||
<div class="form-group"><label>金额(元)</label><input type="number" step="0.01" id="editAmount" value="${record.amount || ''}"></div>
|
<div class="form-group"><label>金额(元)</label><input type="number" step="0.01" id="editAmount" value="${escapeAttr(record.amount ?? '')}"></div>
|
||||||
<div class="form-group"><label>来源</label><input type="text" id="editSource" value="${record.source || ''}"></div>
|
<div class="form-group"><label>来源</label><input type="text" id="editSource" value="${escapeAttr(record.source || '')}"></div>
|
||||||
<div class="form-group"><label>时间</label><input type="datetime-local" id="editTime" value="${localTime}"></div>
|
<div class="form-group"><label>时间</label><input type="datetime-local" id="editTime" value="${escapeAttr(localTime)}"></div>
|
||||||
`;
|
`;
|
||||||
} else if (cat === '红包支出' || cat === '比心' || cat === '其他支出') {
|
} else if (cat === '红包支出' || cat === '比心' || cat === '其他支出') {
|
||||||
document.getElementById('editModalTitle').textContent = '编辑' + cat + '记录';
|
document.getElementById('editModalTitle').textContent = '编辑' + cat + '记录';
|
||||||
let fields = `
|
let fields = `
|
||||||
<div class="form-group"><label>金额(元)</label><input type="number" step="0.01" id="editAmount" value="${record.amount || ''}"></div>
|
<div class="form-group"><label>金额(元)</label><input type="number" step="0.01" id="editAmount" value="${escapeAttr(record.amount ?? '')}"></div>
|
||||||
`;
|
`;
|
||||||
if (cat !== '比心') {
|
if (cat !== '比心') {
|
||||||
fields += `<div class="form-group"><label>目的地</label><input type="text" id="editDest" value="${record.destination || ''}"></div>`;
|
fields += `<div class="form-group"><label>目的地</label><input type="text" id="editDest" value="${escapeAttr(record.destination || '')}"></div>`;
|
||||||
} else {
|
} else {
|
||||||
fields += `<div class="form-group"><label>用途</label><input type="text" id="editDest" value="${record.destination || ''}"></div>`;
|
fields += `<div class="form-group"><label>用途</label><input type="text" id="editDest" value="${escapeAttr(record.destination || '')}"></div>`;
|
||||||
}
|
}
|
||||||
if (cat === '其他支出') {
|
if (cat === '其他支出') {
|
||||||
fields += `<div class="form-group"><label>备注</label><input type="text" id="editNote" value="${record.note || ''}"></div>`;
|
fields += `<div class="form-group"><label>备注</label><input type="text" id="editNote" value="${escapeAttr(record.note || '')}"></div>`;
|
||||||
}
|
}
|
||||||
fields += `<div class="form-group"><label>时间</label><input type="datetime-local" id="editTime" value="${localTime}"></div>`;
|
fields += `<div class="form-group"><label>时间</label><input type="datetime-local" id="editTime" value="${escapeAttr(localTime)}"></div>`;
|
||||||
body.innerHTML = fields;
|
body.innerHTML = fields;
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('editModalTitle').textContent = '编辑记录';
|
document.getElementById('editModalTitle').textContent = '编辑记录';
|
||||||
body.innerHTML = `
|
body.innerHTML = `
|
||||||
<div class="form-group"><label>金额(元)</label><input type="number" step="0.01" id="editAmount" value="${record.amount || ''}"></div>
|
<div class="form-group"><label>金额(元)</label><input type="number" step="0.01" id="editAmount" value="${escapeAttr(record.amount ?? '')}"></div>
|
||||||
<div class="form-group"><label>时间</label><input type="datetime-local" id="editTime" value="${localTime}"></div>
|
<div class="form-group"><label>时间</label><input type="datetime-local" id="editTime" value="${escapeAttr(localTime)}"></div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
// 绑定自动补全
|
// 绑定自动补全
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const ioRouter = require('./routes/io');
|
|||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
app.set('trust proxy', config.trustProxy);
|
||||||
|
|
||||||
// 请求日志中间件
|
// 请求日志中间件
|
||||||
app.use(logger.requestLogger);
|
app.use(logger.requestLogger);
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -11,7 +11,8 @@ if (isProduction && !jwtSecret) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
// 服务器配置
|
// 服务器配置
|
||||||
port: process.env.PORT || 3000,
|
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 配置
|
// JWT 配置
|
||||||
jwtSecret: jwtSecret || 'dev-only-gamer-order-secret',
|
jwtSecret: jwtSecret || 'dev-only-gamer-order-secret',
|
||||||
|
|||||||
+18
-9
@@ -21,6 +21,23 @@ function nullableDbValue(value) {
|
|||||||
return value === undefined || value === null || value === '' ? null : 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) => {
|
router.get('/export', (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
@@ -54,15 +71,7 @@ router.get('/export', (req, res, next) => {
|
|||||||
const BOM = '\uFEFF';
|
const BOM = '\uFEFF';
|
||||||
const cols = ['type', 'category', 'amount', 'quantity', 'unit_price', 'rebate', 'boss', 'partner', 'source', 'destination', 'note', 'created_at'];
|
const cols = ['type', 'category', 'amount', 'quantity', 'unit_price', 'rebate', 'boss', 'partner', 'source', 'destination', 'note', 'created_at'];
|
||||||
const csvRows = records.map(r => {
|
const csvRows = records.map(r => {
|
||||||
return cols.map(col => {
|
return cols.map(col => encodeCsvCell(r[col], col)).join(',');
|
||||||
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(',');
|
|
||||||
});
|
});
|
||||||
logger.info(`用户 ${req.user.username} 导出 CSV 数据: ${records.length} 条`);
|
logger.info(`用户 ${req.user.username} 导出 CSV 数据: ${records.length} 条`);
|
||||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ const logger = {
|
|||||||
const duration = Date.now() - start;
|
const duration = Date.now() - start;
|
||||||
const status = res.statusCode;
|
const status = res.statusCode;
|
||||||
const level = status >= 400 ? 'warn' : 'info';
|
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();
|
next();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = logger;
|
module.exports = logger;
|
||||||
|
|||||||
Reference in New Issue
Block a user