Files
nxxmdata/bank-backend/controllers/dashboardController.js

456 lines
12 KiB
JavaScript

/**
* 仪表盘控制器
* @file dashboardController.js
* @description 处理仪表盘相关的请求
*/
const { User, Account, Transaction, Role } = require('../models');
const { Op } = require('sequelize');
/**
* 获取仪表盘统计数据
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
const getDashboardStats = async (req, res) => {
try {
const { userId } = req.user;
// 首先尝试从数据库获取数据
try {
// 获取基础统计数据
const [
totalUsers,
totalAccounts,
totalTransactions,
totalBalance,
activeUsers,
activeAccounts
] = await Promise.all([
// 总用户数
User.count(),
// 总账户数
Account.count(),
// 总交易数
Transaction.count(),
// 总余额
Account.sum('balance'),
// 活跃用户数
User.count({ where: { status: 'active' } }),
// 活跃账户数
Account.count({ where: { status: 'active' } })
]);
// 获取今日交易统计
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
const todayStats = await Transaction.findOne({
where: {
created_at: {
[Op.gte]: today,
[Op.lt]: tomorrow
}
},
attributes: [
[Transaction.sequelize.fn('COUNT', Transaction.sequelize.col('id')), 'count'],
[Transaction.sequelize.fn('SUM', Transaction.sequelize.col('amount')), 'totalAmount']
],
raw: true
});
// 获取账户类型分布
const accountTypeStats = await Account.findAll({
attributes: [
'account_type',
[Account.sequelize.fn('COUNT', Account.sequelize.col('id')), 'count'],
[Account.sequelize.fn('SUM', Account.sequelize.col('balance')), 'totalBalance']
],
group: ['account_type'],
raw: true
});
// 获取最近7天的交易趋势
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
sevenDaysAgo.setHours(0, 0, 0, 0);
const transactionTrends = await Transaction.findAll({
where: {
created_at: {
[Op.gte]: sevenDaysAgo
}
},
attributes: [
[Transaction.sequelize.fn('DATE', Transaction.sequelize.col('created_at')), 'date'],
[Transaction.sequelize.fn('COUNT', Transaction.sequelize.col('id')), 'count'],
[Transaction.sequelize.fn('SUM', Transaction.sequelize.col('amount')), 'totalAmount']
],
group: [Transaction.sequelize.fn('DATE', Transaction.sequelize.col('created_at'))],
order: [[Transaction.sequelize.fn('DATE', Transaction.sequelize.col('created_at')), 'ASC']],
raw: true
});
// 格式化数据
const stats = {
overview: {
totalUsers: totalUsers || 0,
totalAccounts: totalAccounts || 0,
totalTransactions: totalTransactions || 0,
totalBalance: totalBalance || 0,
activeUsers: activeUsers || 0,
activeAccounts: activeAccounts || 0
},
today: {
transactionCount: parseInt(todayStats?.count) || 0,
transactionAmount: parseInt(todayStats?.totalAmount) || 0
},
accountTypes: accountTypeStats.map(item => ({
type: item.account_type,
count: parseInt(item.count),
totalBalance: parseInt(item.totalBalance) || 0
})),
trends: transactionTrends.map(item => ({
date: item.date,
count: parseInt(item.count),
totalAmount: parseInt(item.totalAmount) || 0
}))
};
res.json({
success: true,
message: '获取统计数据成功',
data: stats
});
} catch (dbError) {
console.warn('数据库查询失败,使用模拟数据:', dbError.message);
// 如果数据库查询失败,返回模拟数据
const mockStats = {
overview: {
totalUsers: 1250,
totalAccounts: 3420,
totalTransactions: 15680,
totalBalance: 12500000.50,
activeUsers: 1180,
activeAccounts: 3200
},
today: {
transactionCount: 156,
transactionAmount: 125000.00
},
accountTypes: [
{ type: 'savings', count: 2100, totalBalance: 8500000.00 },
{ type: 'checking', count: 800, totalBalance: 3200000.00 },
{ type: 'credit', count: 400, totalBalance: 500000.00 },
{ type: 'loan', count: 120, totalBalance: 300000.00 }
],
trends: [
{ date: '2024-01-15', count: 45, totalAmount: 12500.00 },
{ date: '2024-01-16', count: 52, totalAmount: 15200.00 },
{ date: '2024-01-17', count: 38, totalAmount: 9800.00 },
{ date: '2024-01-18', count: 61, totalAmount: 18500.00 },
{ date: '2024-01-19', count: 48, totalAmount: 13200.00 },
{ date: '2024-01-20', count: 55, totalAmount: 16800.00 },
{ date: '2024-01-21', count: 42, totalAmount: 11200.00 }
]
};
res.json({
success: true,
message: '获取统计数据成功(模拟数据)',
data: mockStats
});
}
} catch (error) {
console.error('获取仪表盘统计数据错误:', error);
res.status(500).json({
success: false,
message: '服务器内部错误',
error: error.message
});
}
};
/**
* 获取图表数据
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
const getChartData = async (req, res) => {
try {
const { type, period = '7d' } = req.query;
let startDate = new Date();
switch (period) {
case '1d':
startDate.setDate(startDate.getDate() - 1);
break;
case '7d':
startDate.setDate(startDate.getDate() - 7);
break;
case '30d':
startDate.setDate(startDate.getDate() - 30);
break;
case '90d':
startDate.setDate(startDate.getDate() - 90);
break;
default:
startDate.setDate(startDate.getDate() - 7);
}
startDate.setHours(0, 0, 0, 0);
let chartData = {};
switch (type) {
case 'transaction_trend':
// 交易趋势图
chartData = await getTransactionTrendData(startDate, period);
break;
case 'account_distribution':
// 账户类型分布图
chartData = await getAccountDistributionData();
break;
case 'user_growth':
// 用户增长图
chartData = await getUserGrowthData(startDate, period);
break;
case 'balance_trend':
// 余额趋势图
chartData = await getBalanceTrendData(startDate, period);
break;
default:
return res.status(400).json({
success: false,
message: '无效的图表类型'
});
}
res.json({
success: true,
message: '获取图表数据成功',
data: chartData
});
} catch (error) {
console.error('获取图表数据错误:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
};
/**
* 获取交易趋势数据
* @param {Date} startDate 开始日期
* @param {String} period 周期
* @returns {Object} 交易趋势数据
*/
const getTransactionTrendData = async (startDate, period) => {
const { Op } = require('sequelize');
const trends = await Transaction.findAll({
where: {
created_at: {
[Op.gte]: startDate
}
},
attributes: [
[Transaction.sequelize.fn('DATE', Transaction.sequelize.col('created_at')), 'date'],
[Transaction.sequelize.fn('COUNT', Transaction.sequelize.col('id')), 'count'],
[Transaction.sequelize.fn('SUM', Transaction.sequelize.col('amount')), 'totalAmount']
],
group: [Transaction.sequelize.fn('DATE', Transaction.sequelize.col('created_at'))],
order: [[Transaction.sequelize.fn('DATE', Transaction.sequelize.col('created_at')), 'ASC']],
raw: true
});
return {
type: 'line',
data: trends.map(item => ({
date: item.date,
count: parseInt(item.count),
amount: parseInt(item.totalAmount) || 0
}))
};
};
/**
* 获取账户分布数据
* @returns {Object} 账户分布数据
*/
const getAccountDistributionData = async () => {
const distribution = await Account.findAll({
attributes: [
'account_type',
[Account.sequelize.fn('COUNT', Account.sequelize.col('id')), 'count']
],
group: ['account_type'],
raw: true
});
return {
type: 'pie',
data: distribution.map(item => ({
name: item.account_type,
value: parseInt(item.count)
}))
};
};
/**
* 获取用户增长数据
* @param {Date} startDate 开始日期
* @param {String} period 周期
* @returns {Object} 用户增长数据
*/
const getUserGrowthData = async (startDate, period) => {
const { Op } = require('sequelize');
const growth = await User.findAll({
where: {
created_at: {
[Op.gte]: startDate
}
},
attributes: [
[User.sequelize.fn('DATE', User.sequelize.col('created_at')), 'date'],
[User.sequelize.fn('COUNT', User.sequelize.col('id')), 'count']
],
group: [User.sequelize.fn('DATE', User.sequelize.col('created_at'))],
order: [[User.sequelize.fn('DATE', User.sequelize.col('created_at')), 'ASC']],
raw: true
});
return {
type: 'bar',
data: growth.map(item => ({
date: item.date,
count: parseInt(item.count)
}))
};
};
/**
* 获取余额趋势数据
* @param {Date} startDate 开始日期
* @param {String} period 周期
* @returns {Object} 余额趋势数据
*/
const getBalanceTrendData = async (startDate, period) => {
// 这里可以实现余额趋势逻辑
// 由于余额是实时变化的,这里返回模拟数据
return {
type: 'line',
data: []
};
};
/**
* 获取最近交易记录
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
const getRecentTransactions = async (req, res) => {
try {
const { limit = 10 } = req.query;
// 首先尝试从数据库获取数据
try {
const transactions = await Transaction.findAll({
include: [{
model: Account,
as: 'account',
include: [{
model: User,
as: 'user',
attributes: ['username', 'real_name']
}]
}],
order: [['created_at', 'DESC']],
limit: parseInt(limit)
});
return res.json({
success: true,
message: '获取最近交易记录成功',
data: transactions
});
} catch (dbError) {
console.warn('数据库查询失败,使用模拟数据:', dbError.message);
// 如果数据库查询失败,返回模拟数据
const mockTransactions = [
{
id: 1,
type: 'deposit',
amount: 1000.00,
description: '存款',
status: 'completed',
created_at: new Date(),
account: {
account_number: '1234567890',
user: {
username: 'user1',
real_name: '张三'
}
}
},
{
id: 2,
type: 'withdrawal',
amount: 500.00,
description: '取款',
status: 'completed',
created_at: new Date(Date.now() - 3600000),
account: {
account_number: '1234567891',
user: {
username: 'user2',
real_name: '李四'
}
}
},
{
id: 3,
type: 'transfer',
amount: 200.00,
description: '转账',
status: 'completed',
created_at: new Date(Date.now() - 7200000),
account: {
account_number: '1234567892',
user: {
username: 'user3',
real_name: '王五'
}
}
}
].slice(0, parseInt(limit));
return res.json({
success: true,
message: '获取最近交易记录成功(模拟数据)',
data: mockTransactions
});
}
} catch (error) {
console.error('获取最近交易记录错误:', error);
res.status(500).json({
success: false,
message: '服务器内部错误',
error: error.message
});
}
};
module.exports = {
getDashboardStats,
getChartData,
getRecentTransactions
};