/**
* 验证工具类
* 用于参数验证和数据校验
*/
const Joi = require('joi');
const moment = require('moment');
class ValidationUtils {
constructor() {
this.schemas = this.initializeSchemas();
}
/**
* 初始化验证模式
*/
initializeSchemas() {
return {
// 日期时间验证
birthDateTime: Joi.alternatives().try(
Joi.date().iso(),
Joi.string().pattern(/^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}(:\d{2})?)?$/)
).required().messages({
'any.required': '出生日期时间是必需的',
'date.base': '请提供有效的日期格式',
'string.pattern.base': '日期格式应为 YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss'
}),
// 性别验证
gender: Joi.string().valid('male', 'female', '男', '女').required().messages({
'any.required': '性别是必需的',
'any.only': '性别只能是 male、female、男 或 女'
}),
// 时区验证
timezone: Joi.string().default('Asia/Shanghai').messages({
'string.base': '时区必须是字符串'
}),
// 问题类型验证
questionType: Joi.string().valid(
'career', 'relationship', 'health', 'wealth', 'education', 'general'
).default('general').messages({
'any.only': '问题类型必须是 career、relationship、health、wealth、education 或 general 之一'
}),
// 分析类型验证
analysisType: Joi.string().valid(
'personality', 'career', 'wealth', 'relationship', 'health', 'comprehensive'
).default('comprehensive').messages({
'any.only': '分析类型必须是 personality、career、wealth、relationship、health 或 comprehensive 之一'
}),
// 预测期间验证
forecastPeriod: Joi.string().valid(
'monthly', 'yearly', 'decade', 'custom'
).default('yearly').messages({
'any.only': '预测期间必须是 monthly、yearly、decade 或 custom 之一'
}),
// 年份验证
year: Joi.number().integer().min(1900).max(2100).messages({
'number.base': '年份必须是数字',
'number.integer': '年份必须是整数',
'number.min': '年份不能早于1900年',
'number.max': '年份不能晚于2100年'
}),
// 月份验证
month: Joi.number().integer().min(1).max(12).messages({
'number.base': '月份必须是数字',
'number.integer': '月份必须是整数',
'number.min': '月份不能小于1',
'number.max': '月份不能大于12'
}),
// 占卜方法验证
divinationMethod: Joi.string().valid(
'coin', 'yarrow', 'number', 'time'
).default('coin').messages({
'any.only': '占卜方法必须是 coin、yarrow、number 或 time 之一'
}),
// 问题验证
question: Joi.string().min(1).max(500).required().messages({
'any.required': '问题是必需的',
'string.empty': '问题不能为空',
'string.min': '问题至少需要1个字符',
'string.max': '问题不能超过500个字符'
}),
// 焦点验证
focus: Joi.string().valid(
'general', 'career', 'relationship', 'health', 'wealth', 'decision'
).default('general').messages({
'any.only': '焦点必须是 general、career、relationship、health、wealth 或 decision 之一'
}),
// 学习主题验证
topic: Joi.string().valid(
'yijing_basics', 'hexagram_interpretation', 'bazi_basics',
'ten_gods', 'five_elements', 'spirits', 'patterns', 'forecasting'
).required().messages({
'any.required': '学习主题是必需的',
'any.only': '学习主题必须是预定义的主题之一'
}),
// 学习级别验证
level: Joi.string().valid('beginner', 'intermediate', 'advanced').default('beginner').messages({
'any.only': '学习级别必须是 beginner、intermediate 或 advanced 之一'
}),
// 学习风格验证
learningStyle: Joi.string().valid(
'theoretical', 'practical', 'case_study', 'interactive'
).default('theoretical').messages({
'any.only': '学习风格必须是 theoretical、practical、case_study 或 interactive 之一'
}),
// 案例数据验证
caseData: Joi.object().required().messages({
'any.required': '案例数据是必需的',
'object.base': '案例数据必须是对象'
}),
// 咨询类型验证
consultationType: Joi.string().valid(
'career', 'relationship', 'health', 'comprehensive'
).required().messages({
'any.required': '咨询类型是必需的',
'any.only': '咨询类型必须是 career、relationship、health 或 comprehensive 之一'
})
};
}
/**
* 验证生成卦象的参数
* @param {Object} params - 参数对象
* @returns {Object} 验证结果
*/
validateGenerateHexagram(params) {
const schema = Joi.object({
method: this.schemas.divinationMethod,
question: this.schemas.question,
focus: this.schemas.focus
});
return this.validate(schema, params);
}
/**
* 验证解释卦象的参数
* @param {Object} params - 参数对象
* @returns {Object} 验证结果
*/
validateInterpretHexagram(params) {
const schema = Joi.object({
hexagram_id: Joi.number().integer().min(1).max(64).required().messages({
'any.required': '卦象ID是必需的',
'number.base': '卦象ID必须是数字',
'number.integer': '卦象ID必须是整数',
'number.min': '卦象ID不能小于1',
'number.max': '卦象ID不能大于64'
}),
question: this.schemas.question,
focus: this.schemas.focus,
changed_lines: Joi.array().items(Joi.number().integer().min(1).max(6)).messages({
'array.base': '变爻必须是数组',
'number.base': '变爻位置必须是数字',
'number.integer': '变爻位置必须是整数',
'number.min': '变爻位置不能小于1',
'number.max': '变爻位置不能大于6'
})
});
return this.validate(schema, params);
}
/**
* 验证易经建议的参数
* @param {Object} params - 参数对象
* @returns {Object} 验证结果
*/
validateYijingAdvice(params) {
const schema = Joi.object({
question: this.schemas.question,
question_type: this.schemas.questionType,
context: Joi.string().max(1000).messages({
'string.max': '上下文不能超过1000个字符'
})
});
return this.validate(schema, params);
}
/**
* 验证生成八字的参数
* @param {Object} params - 参数对象
* @returns {Object} 验证结果
*/
validateGenerateBazi(params) {
const schema = Joi.object({
birth_datetime: this.schemas.birthDateTime,
gender: this.schemas.gender,
timezone: this.schemas.timezone
});
return this.validate(schema, params);
}
/**
* 验证分析八字的参数
* @param {Object} params - 参数对象
* @returns {Object} 验证结果
*/
validateAnalyzeBazi(params) {
const schema = Joi.object({
birth_datetime: this.schemas.birthDateTime,
gender: this.schemas.gender,
analysis_type: this.schemas.analysisType,
timezone: this.schemas.timezone
});
return this.validate(schema, params);
}
/**
* 验证八字预测的参数
* @param {Object} params - 参数对象
* @returns {Object} 验证结果
*/
validateBaziForecast(params) {
const schema = Joi.object({
birth_datetime: this.schemas.birthDateTime,
gender: this.schemas.gender,
period: this.schemas.forecastPeriod,
target_year: this.schemas.year,
target_month: this.schemas.month,
timezone: this.schemas.timezone
});
return this.validate(schema, params);
}
/**
* 验证综合分析的参数
* @param {Object} params - 参数对象
* @returns {Object} 验证结果
*/
validateCombinedAnalysis(params) {
const schema = Joi.object({
birth_datetime: this.schemas.birthDateTime,
gender: this.schemas.gender,
question: this.schemas.question,
question_type: this.schemas.questionType,
timezone: this.schemas.timezone
});
return this.validate(schema, params);
}
/**
* 验证命理咨询的参数
* @param {Object} params - 参数对象
* @returns {Object} 验证结果
*/
validateDestinyConsult(params) {
const schema = Joi.object({
birth_datetime: this.schemas.birthDateTime,
gender: this.schemas.gender,
consultation_type: this.schemas.consultationType,
specific_question: Joi.string().max(500).messages({
'string.max': '具体问题不能超过500个字符'
}),
timezone: this.schemas.timezone
});
return this.validate(schema, params);
}
/**
* 验证知识学习的参数
* @param {Object} params - 参数对象
* @returns {Object} 验证结果
*/
validateKnowledgeLearn(params) {
const schema = Joi.object({
topic: this.schemas.topic,
level: this.schemas.level,
learning_style: this.schemas.learningStyle
});
return this.validate(schema, params);
}
/**
* 验证案例研究的参数
* @param {Object} params - 参数对象
* @returns {Object} 验证结果
*/
validateCaseStudy(params) {
const schema = Joi.object({
case_data: this.schemas.caseData,
analysis_focus: Joi.string().max(200).messages({
'string.max': '分析焦点不能超过200个字符'
})
});
return this.validate(schema, params);
}
/**
* 执行验证
* @param {Object} schema - Joi验证模式
* @param {Object} data - 要验证的数据
* @returns {Object} 验证结果
*/
validate(schema, data) {
const { error, value } = schema.validate(data, {
abortEarly: false,
allowUnknown: false,
stripUnknown: true
});
if (error) {
return {
isValid: false,
errors: error.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message,
value: detail.context.value
})),
data: null
};
}
return {
isValid: true,
errors: [],
data: value
};
}
/**
* 验证日期格式
* @param {string} dateString - 日期字符串
* @returns {Object} 验证结果
*/
validateDateFormat(dateString) {
const formats = [
'YYYY-MM-DD',
'YYYY-MM-DD HH:mm',
'YYYY-MM-DD HH:mm:ss',
'YYYY/MM/DD',
'YYYY/MM/DD HH:mm',
'YYYY/MM/DD HH:mm:ss'
];
for (const format of formats) {
const parsed = moment(dateString, format, true);
if (parsed.isValid()) {
return {
isValid: true,
format,
parsed: parsed.toDate(),
message: '日期格式有效'
};
}
}
return {
isValid: false,
format: null,
parsed: null,
message: '无效的日期格式,支持的格式:YYYY-MM-DD [HH:mm[:ss]]'
};
}
/**
* 验证时区
* @param {string} timezone - 时区字符串
* @returns {Object} 验证结果
*/
validateTimezone(timezone) {
try {
const testDate = moment().tz(timezone);
return {
isValid: true,
timezone,
message: '时区有效'
};
} catch (error) {
return {
isValid: false,
timezone,
message: `无效的时区: ${timezone}`
};
}
}
/**
* 验证卦象ID
* @param {number} hexagramId - 卦象ID
* @returns {Object} 验证结果
*/
validateHexagramId(hexagramId) {
const id = parseInt(hexagramId);
if (isNaN(id) || id < 1 || id > 64) {
return {
isValid: false,
message: '卦象ID必须是1-64之间的整数'
};
}
return {
isValid: true,
hexagramId: id,
message: '卦象ID有效'
};
}
/**
* 验证变爻
* @param {Array} changedLines - 变爻数组
* @returns {Object} 验证结果
*/
validateChangedLines(changedLines) {
if (!Array.isArray(changedLines)) {
return {
isValid: false,
message: '变爻必须是数组'
};
}
const validLines = changedLines.filter(line => {
const num = parseInt(line);
return !isNaN(num) && num >= 1 && num <= 6;
});
if (validLines.length !== changedLines.length) {
return {
isValid: false,
message: '变爻位置必须是1-6之间的整数'
};
}
// 去重
const uniqueLines = [...new Set(validLines)];
return {
isValid: true,
changedLines: uniqueLines,
message: '变爻有效'
};
}
/**
* 验证性别
* @param {string} gender - 性别
* @returns {Object} 验证结果
*/
validateGender(gender) {
const normalizedGender = this.normalizeGender(gender);
if (!normalizedGender) {
return {
isValid: false,
message: '性别只能是 male、female、男 或 女'
};
}
return {
isValid: true,
gender: normalizedGender,
message: '性别有效'
};
}
/**
* 标准化性别
* @param {string} gender - 性别
* @returns {string|null} 标准化后的性别
*/
normalizeGender(gender) {
const genderMap = {
'male': 'male',
'female': 'female',
'男': 'male',
'女': 'female',
'M': 'male',
'F': 'female',
'm': 'male',
'f': 'female'
};
return genderMap[gender] || null;
}
/**
* 清理和验证输入数据
* @param {Object} data - 输入数据
* @returns {Object} 清理后的数据
*/
sanitizeInput(data) {
const sanitized = {};
for (const [key, value] of Object.entries(data)) {
if (typeof value === 'string') {
// 去除首尾空格
sanitized[key] = value.trim();
// 防止XSS攻击,移除潜在的脚本标签
sanitized[key] = sanitized[key].replace(/<script[^>]*>.*?<\/script>/gi, '');
sanitized[key] = sanitized[key].replace(/javascript:/gi, '');
sanitized[key] = sanitized[key].replace(/on\w+=/gi, '');
} else {
sanitized[key] = value;
}
}
return sanitized;
}
/**
* 创建错误响应
* @param {string} message - 错误消息
* @param {Array} details - 错误详情
* @returns {Object} 错误响应
*/
createErrorResponse(message, details = []) {
return {
success: false,
error: {
message,
details,
timestamp: new Date().toISOString()
}
};
}
/**
* 创建成功响应
* @param {*} data - 响应数据
* @param {string} message - 成功消息
* @returns {Object} 成功响应
*/
createSuccessResponse(data, message = '操作成功') {
return {
success: true,
data,
message,
timestamp: new Date().toISOString()
};
}
}
module.exports = ValidationUtils;