const Joi = require('joi'); const { AppError } = require('./errorHandler'); const { ERROR_CODES, HTTP_STATUS } = require('../config/constants'); // 验证中间件工厂函数 const validate = (schema) => { return (req, res, next) => { const { error } = schema.validate(req.body, { abortEarly: false }); if (error) { const errorMessage = error.details.map(detail => detail.message).join(', '); return next(new AppError(errorMessage, HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR)); } next(); }; }; // 查询参数验证中间件 const validateQuery = (schema) => { return (req, res, next) => { const { error } = schema.validate(req.query, { abortEarly: false }); if (error) { const errorMessage = error.details.map(detail => detail.message).join(', '); return next(new AppError(errorMessage, HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR)); } next(); }; }; // 路径参数验证中间件 const validateParams = (schema) => { return (req, res, next) => { const { error } = schema.validate(req.params, { abortEarly: false }); if (error) { const errorMessage = error.details.map(detail => detail.message).join(', '); return next(new AppError(errorMessage, HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR)); } next(); }; }; // 通用验证规则 const commonSchemas = { // ID验证 id: Joi.number().integer().positive().required().messages({ 'number.base': 'ID必须是数字', 'number.integer': 'ID必须是整数', 'number.positive': 'ID必须是正数', 'any.required': 'ID是必需的' }), // 分页验证 pagination: Joi.object({ page: Joi.number().integer().min(1).default(1).messages({ 'number.base': '页码必须是数字', 'number.integer': '页码必须是整数', 'number.min': '页码必须大于0' }), limit: Joi.number().integer().min(1).max(100).default(10).messages({ 'number.base': '每页数量必须是数字', 'number.integer': '每页数量必须是整数', 'number.min': '每页数量必须大于0', 'number.max': '每页数量不能超过100' }) }) }; // 用户相关验证规则 const userSchemas = { // 用户注册 register: Joi.object({ username: Joi.string().alphanum().min(3).max(30).required().messages({ 'string.base': '用户名必须是字符串', 'string.alphanum': '用户名只能包含字母和数字', 'string.min': '用户名至少3个字符', 'string.max': '用户名最多30个字符', 'any.required': '用户名是必需的' }), password: Joi.string().min(6).max(128).required().messages({ 'string.base': '密码必须是字符串', 'string.min': '密码至少6个字符', 'string.max': '密码最多128个字符', 'any.required': '密码是必需的' }), phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required().messages({ 'string.pattern.base': '手机号格式不正确', 'any.required': '手机号是必需的' }), // 可选字段,注册时不需要填写 real_name: Joi.string().max(50).allow('').optional().messages({ 'string.max': '真实姓名最多50个字符' }), role: Joi.string().valid('admin', 'user').default('user').messages({ 'any.only': '角色只能是admin或user' }) }), // 用户登录 login: Joi.object({ username: Joi.string().required().messages({ 'any.required': '用户名是必需的' }), password: Joi.string().required().messages({ 'any.required': '密码是必需的' }) }) }; // 转账相关验证规则 const transferSchemas = { // 转账查询参数 query: Joi.object({ page: Joi.number().integer().min(1).default(1).messages({ 'number.base': '页码必须是数字', 'number.integer': '页码必须是整数', 'number.min': '页码必须大于0' }), limit: Joi.number().integer().min(1).max(100).default(10).messages({ 'number.base': '每页数量必须是数字', 'number.integer': '每页数量必须是整数', 'number.min': '每页数量必须大于0', 'number.max': '每页数量不能超过100' }), status: Joi.string().valid('pending', 'confirmed', 'rejected', 'cancelled').allow('').messages({ 'any.only': '状态值无效' }), type: Joi.string().valid('user_to_user', 'system_to_user', 'user_to_system').allow('').messages({ 'any.only': '转账类型无效' }), search: Joi.string().allow('').max(100).messages({ 'string.max': '搜索关键词最多100个字符' }), transfer_type: Joi.string().valid('user_to_user', 'system_to_user', 'user_to_system').allow('').messages({ 'any.only': '转账类型无效' }), start_date: Joi.date().iso().allow('').messages({ 'date.format': '开始日期格式不正确' }), end_date: Joi.date().iso().allow('').messages({ 'date.format': '结束日期格式不正确' }), sort: Joi.string().valid('id', 'amount', 'created_at', 'updated_at', 'status').allow('').messages({ 'any.only': '排序字段无效,只支持: id, amount, created_at, updated_at, status' }), order: Joi.string().valid('asc', 'desc').allow('').messages({ 'any.only': '排序方向无效,只支持: asc, desc' }), // 优先显示待处理转账参数 show_pending: Joi.alternatives().try( Joi.boolean(), Joi.string().valid('true', 'false', '') ).allow('').messages({ 'alternatives.match': 'show_pending参数只能是布尔值或字符串true/false' }) }), // 创建转账 create: Joi.object({ to_user_id: Joi.number().integer().positive().required().messages({ 'number.base': '收款用户ID必须是数字', 'number.integer': '收款用户ID必须是整数', 'number.positive': '收款用户ID必须是正数', 'any.required': '收款用户ID是必需的' }), amount: Joi.number().positive().precision(2).required().messages({ 'number.base': '金额必须是数字', 'number.positive': '金额必须是正数', 'any.required': '金额是必需的' }), transfer_type: Joi.string().valid('user_to_user', 'system_to_user', 'user_to_system').required().messages({ 'any.only': '转账类型无效', 'any.required': '转账类型是必需的' }), description: Joi.string().max(500).allow('').messages({ 'string.max': '描述最多500个字符' }), voucher_url: Joi.string().uri().allow('').messages({ 'string.uri': '凭证URL格式不正确' }) }), // 确认转账 confirm: Joi.object({ transfer_id: Joi.number().integer().positive().required().messages({ 'number.base': '转账ID必须是数字', 'number.integer': '转账ID必须是整数', 'number.positive': '转账ID必须是正数', 'any.required': '转账ID是必需的' }), note: Joi.string().max(500).allow('').messages({ 'string.max': '备注最多500个字符' }) }), // 拒绝转账 reject: Joi.object({ transfer_id: Joi.number().integer().positive().required().messages({ 'number.base': '转账ID必须是数字', 'number.integer': '转账ID必须是整数', 'number.positive': '转账ID必须是正数', 'any.required': '转账ID是必需的' }), note: Joi.string().max(500).allow('').messages({ 'string.max': '备注最多500个字符' }) }) }; // 系统设置相关验证规则 const systemSchemas = { updateSettings: Joi.object({ site_name: Joi.string().max(100).optional(), site_description: Joi.string().max(500).optional(), contact_phone: Joi.string().max(20).optional(), maintenance_mode: Joi.boolean().optional(), max_transfer_amount: Joi.number().positive().optional(), min_transfer_amount: Joi.number().positive().optional(), transfer_fee_rate: Joi.number().min(0).max(1).optional() }) }; // 导出所有验证规则 module.exports = { validate, validateQuery, validateParams, commonSchemas, userSchemas, transferSchemas, systemSchemas };