const VALID_RECORD_TYPES = ['income', 'expense']; const VALID_STATS_PERIODS = ['week', 'month', 'year']; const VALID_STATS_TYPES = ['income', 'expense', 'all']; const MONTH_PATTERN = /^\d{4}-\d{2}$/; const DATE_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}( \d{2}:\d{2})?$/; function isNonEmptyString(value) { return typeof value === 'string' && value.trim().length > 0; } function isValidMonth(value) { return typeof value === 'string' && MONTH_PATTERN.test(value); } function isValidDateTime(value) { return typeof value === 'string' && DATE_TIME_PATTERN.test(value); } function normalizeNote(value) { return value === undefined || value === null ? '' : value; } function validateRecordPayload(payload) { if (!payload || typeof payload !== 'object') { return '请求数据格式错误'; } const { type, amount, category, date, note } = payload; if (!VALID_RECORD_TYPES.includes(type)) { return '账目类型错误'; } if (!Number.isFinite(amount) || amount <= 0) { return '金额需大于0'; } if (!isNonEmptyString(category)) { return '请填写分类'; } if (!isValidDateTime(date)) { return '日期格式错误'; } if (note !== undefined && note !== null && typeof note !== 'string') { return '备注格式错误'; } return null; } function validateRecurringPayload(payload) { if (!payload || typeof payload !== 'object') { return '请求数据格式错误'; } const { type, amount, category, day, note, is_active } = payload; if (!VALID_RECORD_TYPES.includes(type)) { return '账单类型错误'; } if (!Number.isFinite(amount) || amount <= 0) { return '金额需大于0'; } if (!isNonEmptyString(category)) { return '请填写分类'; } if (!Number.isInteger(day) || day < 1 || day > 28) { return '日期需在1到28之间'; } if (note !== undefined && note !== null && typeof note !== 'string') { return '备注格式错误'; } if (is_active !== undefined && ![0, 1, true, false].includes(is_active)) { return '启用状态错误'; } return null; } function validateCategoryPayload(payload) { if (!payload || typeof payload !== 'object') { return '请求数据格式错误'; } const { type, name, icon } = payload; if (!VALID_RECORD_TYPES.includes(type)) { return '分类类型错误'; } if (!isNonEmptyString(name)) { return '请填写分类名称'; } if (!isNonEmptyString(icon)) { return '请选择分类图标'; } return null; } function validateImportPayload(payload) { if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { return '导入数据格式错误'; } const { records, recurring, customCategories } = payload; if (records !== undefined) { if (!Array.isArray(records)) { return 'records 必须是数组'; } for (const record of records) { const error = validateRecordPayload(record); if (error) { return `records 数据错误: ${error}`; } } } if (recurring !== undefined) { if (!Array.isArray(recurring)) { return 'recurring 必须是数组'; } for (const bill of recurring) { const error = validateRecurringPayload(bill); if (error) { return `recurring 数据错误: ${error}`; } } } if (customCategories !== undefined) { if (!Array.isArray(customCategories)) { return 'customCategories 必须是数组'; } for (const category of customCategories) { const error = validateCategoryPayload(category); if (error) { return `customCategories 数据错误: ${error}`; } } } return null; } module.exports = { isNonEmptyString, isValidMonth, isValidDateTime, normalizeNote, validateRecordPayload, validateRecurringPayload, validateCategoryPayload, validateImportPayload, VALID_RECORD_TYPES, VALID_STATS_PERIODS, VALID_STATS_TYPES, };