初次提交

This commit is contained in:
2025-09-04 10:49:10 +08:00
commit e704c8abca
26 changed files with 8917 additions and 0 deletions

171
middleware/agentAuth.js Normal file
View 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
View 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
View 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
View 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
};