/** * 应用错误类 */ class AppError extends Error { constructor(message, status = 400) { super(message); this.status = status; this.expose = true; // 允许向客户端暴露错误信息 } } /** * 日期范围辅助函数 */ function getMonthRange(qYear, qMonth) { const now = new Date(); const y = Number(qYear) || now.getFullYear(); const m = Number(qMonth) || (now.getMonth() + 1); const startDate = `${y}-${String(m).padStart(2, '0')}-01`; const nm = m + 1 > 12 ? 1 : m + 1; const ny = m + 1 > 12 ? y + 1 : y; const endDate = `${ny}-${String(nm).padStart(2, '0')}-01`; return { startDate, endDate }; } /** * CSV行解析(处理引号内的逗号) */ function parseCSVLine(line) { const result = []; let current = ''; let inQuotes = false; for (let i = 0; i < line.length; i++) { const ch = line[i]; if (inQuotes) { if (ch === '"' && line[i + 1] === '"') { current += '"'; i++; } else if (ch === '"') { inQuotes = false; } else { current += ch; } } else { if (ch === '"') { inQuotes = true; } else if (ch === ',') { result.push(current); current = ''; } else { current += ch; } } } result.push(current); return result; } /** * 输入验证函数 */ function validateRequired(obj, fields) { const missing = fields.filter(f => obj[f] === undefined || obj[f] === null || obj[f] === ''); if (missing.length > 0) { throw new AppError(`缺少必填字段: ${missing.join(', ')}`, 400); } } function validateNumber(value, field, min, max) { const num = Number(value); if (!Number.isFinite(num)) { throw new AppError(`${field} 必须是数字`, 400); } if (min !== undefined && num < min) { throw new AppError(`${field} 不能小于 ${min}`, 400); } if (max !== undefined && num > max) { throw new AppError(`${field} 不能大于 ${max}`, 400); } return num; } function validateString(value, field, maxLength) { if (typeof value !== 'string') { throw new AppError(`${field} 必须是字符串`, 400); } if (maxLength && value.length > maxLength) { throw new AppError(`${field} 长度不能超过 ${maxLength}`, 400); } return value.trim(); } function optionalString(value, field, maxLength) { if (value === undefined || value === null || value === '') return null; return validateString(String(value), field, maxLength); } function validateInList(value, field, list) { if (!list.includes(value)) { throw new AppError(`${field} 值无效`, 400); } return value; } function validatePage(value, defaultValue = 1) { return Math.max(1, Number.parseInt(value, 10) || defaultValue); } function validateLimit(value, defaultValue, maxLimit) { const limit = Number.parseInt(value, 10) || defaultValue; return Math.min(Math.max(1, limit), maxLimit); } function validateIsoDate(value, field) { if (value === undefined || value === null || value === '') return new Date().toISOString(); const date = new Date(value); if (Number.isNaN(date.getTime())) { throw new AppError(`${field} 格式无效`, 400); } return date.toISOString(); } module.exports = { AppError, getMonthRange, parseCSVLine, validateRequired, validateNumber, validateString, optionalString, validateInList, validatePage, validateLimit, validateIsoDate };