初次提交
This commit is contained in:
		
							
								
								
									
										171
									
								
								middleware/agentAuth.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								middleware/agentAuth.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,171 @@ | ||||
| const jwt = require('jsonwebtoken'); | ||||
| const { getDB } = require('../database'); | ||||
| const { logger } = require('../config/logger'); | ||||
|  | ||||
| // JWT密钥 | ||||
| const JWT_SECRET = process.env.JWT_SECRET || 'agent_jwt_secret_key_2024'; | ||||
|  | ||||
| /** | ||||
|  * 代理身份验证中间件 | ||||
|  * 验证JWT token并确保用户是激活的代理 | ||||
|  */ | ||||
| const agentAuth = async (req, res, next) => { | ||||
|   try { | ||||
|     const token = req.headers.authorization?.replace('Bearer ', ''); | ||||
|      | ||||
|     if (!token) { | ||||
|       return res.status(401).json({ | ||||
|         success: false, | ||||
|         message: '未提供认证令牌' | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     // 验证JWT token | ||||
|     const decoded = jwt.verify(token, JWT_SECRET); | ||||
|      | ||||
|     // 检查是否是代理角色 | ||||
|     if (decoded.role !== 'agent') { | ||||
|       return res.status(403).json({ | ||||
|         success: false, | ||||
|         message: '权限不足,需要代理身份' | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     // 查询代理信息确认状态 | ||||
|     const [agents] = await getDB().execute(` | ||||
|       SELECT  | ||||
|         ra.id as agent_id, | ||||
|         ra.user_id, | ||||
|         ra.agent_code, | ||||
|         ra.status, | ||||
|         ra.region_id, | ||||
|         u.phone, | ||||
|         u.real_name | ||||
|       FROM regional_agents ra | ||||
|       LEFT JOIN users u ON ra.user_id = u.id | ||||
|       WHERE ra.id = ? | ||||
|     `, [decoded.agentId]); | ||||
|  | ||||
|     if (agents.length === 0) { | ||||
|       return res.status(401).json({ | ||||
|         success: false, | ||||
|         message: '代理账号不存在' | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     const agent = agents[0]; | ||||
|  | ||||
|     // 检查代理状态 | ||||
|     if (agent.status !== 'active') { | ||||
|       return res.status(403).json({ | ||||
|         success: false, | ||||
|         message: '代理账号已被禁用或未激活' | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     // 将代理信息添加到请求对象中 | ||||
|     req.agent = { | ||||
|       id: agent.agent_id, | ||||
|       userId: agent.user_id, | ||||
|       agentCode: agent.agent_code, | ||||
|       regionId: agent.region_id, | ||||
|       phone: agent.phone, | ||||
|       realName: agent.real_name | ||||
|     }; | ||||
|  | ||||
|     req.user = { | ||||
|       id: agent.user_id, | ||||
|       role: 'agent' | ||||
|     }; | ||||
|  | ||||
|     next(); | ||||
|  | ||||
|   } catch (error) { | ||||
|     if (error.name === 'JsonWebTokenError') { | ||||
|       return res.status(401).json({ | ||||
|         success: false, | ||||
|         message: '无效的认证令牌' | ||||
|       }); | ||||
|     } | ||||
|      | ||||
|     if (error.name === 'TokenExpiredError') { | ||||
|       return res.status(401).json({ | ||||
|         success: false, | ||||
|         message: '认证令牌已过期,请重新登录' | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     logger.error('代理身份验证失败', { | ||||
|       error: error.message, | ||||
|       stack: error.stack, | ||||
|       ip: req.ip | ||||
|     }); | ||||
|      | ||||
|     res.status(500).json({ | ||||
|       success: false, | ||||
|       message: '身份验证失败' | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 可选的代理身份验证中间件 | ||||
|  * 如果提供了token则验证,否则继续执行 | ||||
|  */ | ||||
| const optionalAgentAuth = async (req, res, next) => { | ||||
|   try { | ||||
|     const token = req.headers.authorization?.replace('Bearer ', ''); | ||||
|      | ||||
|     if (!token) { | ||||
|       return next(); | ||||
|     } | ||||
|  | ||||
|     // 验证JWT token | ||||
|     const decoded = jwt.verify(token, JWT_SECRET); | ||||
|      | ||||
|     if (decoded.role === 'agent') { | ||||
|       // 查询代理信息 | ||||
|       const [agents] = await getDB().execute(` | ||||
|         SELECT  | ||||
|           ra.id as agent_id, | ||||
|           ra.user_id, | ||||
|           ra.agent_code, | ||||
|           ra.status, | ||||
|           ra.region_id, | ||||
|           u.phone, | ||||
|           u.real_name | ||||
|         FROM regional_agents ra | ||||
|         LEFT JOIN users u ON ra.user_id = u.id | ||||
|         WHERE ra.id = ? AND ra.status = 'active' | ||||
|       `, [decoded.agentId]); | ||||
|  | ||||
|       if (agents.length > 0) { | ||||
|         const agent = agents[0]; | ||||
|         req.agent = { | ||||
|           id: agent.agent_id, | ||||
|           userId: agent.user_id, | ||||
|           agentCode: agent.agent_code, | ||||
|           regionId: agent.region_id, | ||||
|           phone: agent.phone, | ||||
|           realName: agent.real_name | ||||
|         }; | ||||
|  | ||||
|         req.user = { | ||||
|           id: agent.user_id, | ||||
|           role: 'agent' | ||||
|         }; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     next(); | ||||
|  | ||||
|   } catch (error) { | ||||
|     // 可选验证失败时不阻止请求继续 | ||||
|     next(); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| module.exports = { | ||||
|   agentAuth, | ||||
|   optionalAgentAuth | ||||
| }; | ||||
							
								
								
									
										112
									
								
								middleware/auth.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								middleware/auth.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| const jwt = require('jsonwebtoken'); | ||||
| const { getDB } = require('../database'); | ||||
|  | ||||
| const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; // 在生产环境中应该使用环境变量 | ||||
|  | ||||
| /** | ||||
|  * 用户认证中间件 | ||||
|  * 验证JWT令牌并检查用户状态(包括是否被拉黑) | ||||
|  */ | ||||
| const auth = async (req, res, next) => { | ||||
|   try { | ||||
|     const token = req.header('Authorization')?.replace('Bearer ', ''); | ||||
|      | ||||
|     if (!token) { | ||||
|       return res.status(401).json({ success: false, message: '未提供认证令牌' }); | ||||
|     } | ||||
|  | ||||
|     const decoded = jwt.verify(token, JWT_SECRET); | ||||
|     const db = getDB(); | ||||
|     const [users] = await db.execute('SELECT * FROM users WHERE id = ?', [decoded.userId]); | ||||
|      | ||||
|     if (users.length === 0) { | ||||
|       return res.status(401).json({ success: false, message: '用户不存在' }); | ||||
|     } | ||||
|  | ||||
|     const user = users[0]; | ||||
|      | ||||
|     // 检查用户是否被拉黑 | ||||
|     if (user.is_blacklisted) { | ||||
|       return res.status(403).json({  | ||||
|         success: false,  | ||||
|         message: '账户已被拉黑,请联系管理员',  | ||||
|         code: 'USER_BLACKLISTED'  | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     // 检查支付状态(管理员除外) | ||||
|     if (user.role !== 'admin' && user.payment_status === 'unpaid') { | ||||
|       console.log(11111); | ||||
|        | ||||
|       return res.status(200).json({  | ||||
|         success: false,  | ||||
|         message: '您的账户尚未激活,请完成支付后再使用', | ||||
|         code: 'PAYMENT_REQUIRED', | ||||
|         needPayment: true, | ||||
|         userId: user.id | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     req.user = user; | ||||
|     next(); | ||||
|   } catch (error) { | ||||
|     res.status(401).json({ success: false, message: '无效的认证令牌' }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // 管理员认证中间件 | ||||
| const adminAuth = (req, res, next) => { | ||||
|   if (req.user.role !== 'admin') { | ||||
|     return res.status(403).json({ success: false, message: '需要管理员权限' }); | ||||
|   } | ||||
|   next(); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 支付认证中间件 | ||||
|  * 只验证JWT令牌和用户状态,不检查支付状态 | ||||
|  * 用于支付相关接口,允许未支付用户创建支付订单 | ||||
|  */ | ||||
| const paymentAuth = async (req, res, next) => { | ||||
|   try { | ||||
|     const token = req.header('Authorization')?.replace('Bearer ', ''); | ||||
|      | ||||
|     if (!token) { | ||||
|       return res.status(401).json({ success: false, message: '未提供认证令牌' }); | ||||
|     } | ||||
|  | ||||
|     const decoded = jwt.verify(token, JWT_SECRET); | ||||
|     const db = getDB(); | ||||
|     const [users] = await db.execute('SELECT * FROM users WHERE id = ?', [decoded.userId]); | ||||
|      | ||||
|     if (users.length === 0) { | ||||
|       return res.status(401).json({ success: false, message: '用户不存在' }); | ||||
|     } | ||||
|  | ||||
|     const user = users[0]; | ||||
|      | ||||
|     // 检查用户是否被拉黑 | ||||
|     if (user.is_blacklisted) { | ||||
|       return res.status(403).json({  | ||||
|         success: false,  | ||||
|         message: '账户已被拉黑,请联系管理员',  | ||||
|         code: 'USER_BLACKLISTED'  | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     // 注意:这里不检查支付状态,允许未支付用户创建支付订单 | ||||
|     req.user = user; | ||||
|     next(); | ||||
|   } catch (error) { | ||||
|     console.error('支付认证失败:', error); | ||||
|     if (error.name === 'JsonWebTokenError') { | ||||
|       return res.status(401).json({ success: false, message: '无效的认证令牌' }); | ||||
|     } | ||||
|     if (error.name === 'TokenExpiredError') { | ||||
|       return res.status(401).json({ success: false, message: '认证令牌已过期' }); | ||||
|     } | ||||
|     return res.status(500).json({ success: false, message: '认证失败' }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| module.exports = { auth, adminAuth, paymentAuth, JWT_SECRET }; | ||||
							
								
								
									
										129
									
								
								middleware/errorHandler.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								middleware/errorHandler.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| const { logger } = require('../config/logger'); | ||||
| const { ERROR_CODES, HTTP_STATUS } = require('../config/constants'); | ||||
|  | ||||
| // 全局错误处理中间件 | ||||
| const errorHandler = (err, req, res, next) => { | ||||
|   let error = { ...err }; | ||||
|   error.message = err.message; | ||||
|  | ||||
|   // 记录错误日志 | ||||
|   logger.error('Error occurred:', { | ||||
|     message: err.message, | ||||
|     stack: err.stack, | ||||
|     url: req.originalUrl, | ||||
|     method: req.method, | ||||
|     ip: req.ip, | ||||
|     userAgent: req.get('User-Agent'), | ||||
|     userId: req.user?.id | ||||
|   }); | ||||
|  | ||||
|   // MySQL错误处理 | ||||
|   if (err.code) { | ||||
|     switch (err.code) { | ||||
|       case 'ER_DUP_ENTRY': | ||||
|         error.message = '数据已存在'; | ||||
|         error.statusCode = HTTP_STATUS.CONFLICT; | ||||
|         error.errorCode = ERROR_CODES.DUPLICATE_ENTRY; | ||||
|         break; | ||||
|       case 'ER_NO_REFERENCED_ROW_2': | ||||
|         error.message = '关联数据不存在'; | ||||
|         error.statusCode = HTTP_STATUS.BAD_REQUEST; | ||||
|         error.errorCode = ERROR_CODES.VALIDATION_ERROR; | ||||
|         break; | ||||
|       case 'ER_ROW_IS_REFERENCED_2': | ||||
|         error.message = '数据正在被使用,无法删除'; | ||||
|         error.statusCode = HTTP_STATUS.CONFLICT; | ||||
|         error.errorCode = ERROR_CODES.VALIDATION_ERROR; | ||||
|         break; | ||||
|       case 'ECONNREFUSED': | ||||
|         error.message = '数据库连接失败'; | ||||
|         error.statusCode = HTTP_STATUS.INTERNAL_SERVER_ERROR; | ||||
|         error.errorCode = ERROR_CODES.DATABASE_ERROR; | ||||
|         break; | ||||
|       default: | ||||
|         error.message = '数据库操作失败'; | ||||
|         error.statusCode = HTTP_STATUS.INTERNAL_SERVER_ERROR; | ||||
|         error.errorCode = ERROR_CODES.DATABASE_ERROR; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // JWT错误处理 | ||||
|   if (err.name === 'JsonWebTokenError') { | ||||
|     error.message = '无效的访问令牌'; | ||||
|     error.statusCode = HTTP_STATUS.UNAUTHORIZED; | ||||
|     error.errorCode = ERROR_CODES.AUTHENTICATION_ERROR; | ||||
|   } | ||||
|  | ||||
|   if (err.name === 'TokenExpiredError') { | ||||
|     error.message = '访问令牌已过期'; | ||||
|     error.statusCode = HTTP_STATUS.UNAUTHORIZED; | ||||
|     error.errorCode = ERROR_CODES.AUTHENTICATION_ERROR; | ||||
|   } | ||||
|  | ||||
|   // 参数验证错误 | ||||
|   if (err.name === 'ValidationError' || err.isJoi) { | ||||
|     const message = err.details ? err.details.map(detail => detail.message).join(', ') : err.message; | ||||
|     error.message = `参数验证失败: ${message}`; | ||||
|     error.statusCode = HTTP_STATUS.BAD_REQUEST; | ||||
|     error.errorCode = ERROR_CODES.VALIDATION_ERROR; | ||||
|   } | ||||
|  | ||||
|   // 业务逻辑错误处理 | ||||
|   if (err.message === '余额不足') { | ||||
|     error.message = '用户积分余额不足,无法完成转账操作。请先为用户充值积分或选择其他用户。'; | ||||
|     error.statusCode = HTTP_STATUS.BAD_REQUEST; | ||||
|     error.errorCode = ERROR_CODES.VALIDATION_ERROR; | ||||
|   } | ||||
|  | ||||
|   if (err.message === '用户不存在') { | ||||
|     error.message = '指定的用户不存在,请检查用户信息后重试。'; | ||||
|     error.statusCode = HTTP_STATUS.BAD_REQUEST; | ||||
|     error.errorCode = ERROR_CODES.VALIDATION_ERROR; | ||||
|   } | ||||
|  | ||||
|   // 自定义错误 | ||||
|   if (err.statusCode) { | ||||
|     error.statusCode = err.statusCode; | ||||
|     error.errorCode = err.errorCode || ERROR_CODES.INTERNAL_ERROR; | ||||
|   } | ||||
|  | ||||
|   // 默认错误 | ||||
|   const statusCode = error.statusCode || HTTP_STATUS.INTERNAL_SERVER_ERROR; | ||||
|   const errorCode = error.errorCode || ERROR_CODES.INTERNAL_ERROR; | ||||
|   const message = error.message || '服务器内部错误'; | ||||
|  | ||||
|   res.status(statusCode).json({ | ||||
|     success: false, | ||||
|     error: { | ||||
|       code: errorCode, | ||||
|       message: message | ||||
|     }, | ||||
|     ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // 404错误处理 | ||||
| const notFound = (req, res, next) => { | ||||
|   const error = new Error(`路径 ${req.originalUrl} 未找到`); | ||||
|   error.statusCode = HTTP_STATUS.NOT_FOUND; | ||||
|   error.errorCode = ERROR_CODES.NOT_FOUND; | ||||
|   next(error); | ||||
| }; | ||||
|  | ||||
| // 自定义错误类 | ||||
| class AppError extends Error { | ||||
|   constructor(message, statusCode, errorCode) { | ||||
|     super(message); | ||||
|     this.statusCode = statusCode; | ||||
|     this.errorCode = errorCode; | ||||
|     this.isOperational = true; | ||||
|  | ||||
|     Error.captureStackTrace(this, this.constructor); | ||||
|   } | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   errorHandler, | ||||
|   notFound, | ||||
|   AppError | ||||
| }; | ||||
							
								
								
									
										230
									
								
								middleware/validation.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								middleware/validation.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,230 @@ | ||||
| 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 | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user