修改保险后端代码,政府前端代码
This commit is contained in:
@@ -140,9 +140,11 @@ exports.getAccounts = async (req, res) => {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取账户列表错误:', error);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
message: '服务器内部错误',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
301
bank-backend/controllers/authController.js
Normal file
301
bank-backend/controllers/authController.js
Normal file
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* 认证控制器
|
||||
* @file authController.js
|
||||
* @description 处理用户认证相关的请求
|
||||
*/
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { User, Role } = require('../models');
|
||||
const { validationResult } = require('express-validator');
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const login = async (req, res) => {
|
||||
try {
|
||||
// 验证请求参数
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { username, password } = req.body;
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findOne({
|
||||
where: { username },
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if (user.status !== 'active') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '账户已被禁用,请联系管理员'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
const isValidPassword = await user.validPassword(password);
|
||||
if (!isValidPassword) {
|
||||
// 增加登录失败次数
|
||||
user.login_attempts += 1;
|
||||
if (user.login_attempts >= 5) {
|
||||
user.status = 'locked';
|
||||
user.locked_until = new Date(Date.now() + 30 * 60 * 1000); // 锁定30分钟
|
||||
}
|
||||
await user.save();
|
||||
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 重置登录失败次数
|
||||
user.login_attempts = 0;
|
||||
user.locked_until = null;
|
||||
user.last_login = new Date();
|
||||
await user.save();
|
||||
|
||||
// 生成JWT令牌
|
||||
const token = jwt.sign(
|
||||
{
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
role: user.role?.name || 'user'
|
||||
},
|
||||
process.env.JWT_SECRET || 'your_jwt_secret_key_here',
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
||||
);
|
||||
|
||||
// 返回成功响应
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.real_name,
|
||||
email: user.email,
|
||||
phone: user.phone,
|
||||
role: user.role?.name || 'user',
|
||||
avatar: user.avatar,
|
||||
lastLogin: user.last_login
|
||||
},
|
||||
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('登录错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户登出
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const logout = async (req, res) => {
|
||||
try {
|
||||
// 在实际应用中,可以将token加入黑名单
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登出成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('登出错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const refreshToken = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.user;
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findByPk(userId, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user || user.status !== 'active') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户不存在或已被禁用'
|
||||
});
|
||||
}
|
||||
|
||||
// 生成新的JWT令牌
|
||||
const token = jwt.sign(
|
||||
{
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
role: user.role?.name || 'user'
|
||||
},
|
||||
process.env.JWT_SECRET || 'your_jwt_secret_key_here',
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '令牌刷新成功',
|
||||
data: {
|
||||
token,
|
||||
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('刷新令牌错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const getCurrentUser = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.user;
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findByPk(userId, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取用户信息成功',
|
||||
data: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.real_name,
|
||||
email: user.email,
|
||||
phone: user.phone,
|
||||
role: user.role?.name || 'user',
|
||||
avatar: user.avatar,
|
||||
status: user.status,
|
||||
lastLogin: user.last_login,
|
||||
createdAt: user.created_at
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取用户信息错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const changePassword = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { userId } = req.user;
|
||||
const { oldPassword, newPassword } = req.body;
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findByPk(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证旧密码
|
||||
const isValidPassword = await user.validPassword(oldPassword);
|
||||
if (!isValidPassword) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '原密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
user.password = newPassword;
|
||||
await user.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '密码修改成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('修改密码错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
login,
|
||||
logout,
|
||||
refreshToken,
|
||||
getCurrentUser,
|
||||
changePassword
|
||||
};
|
||||
456
bank-backend/controllers/dashboardController.js
Normal file
456
bank-backend/controllers/dashboardController.js
Normal file
@@ -0,0 +1,456 @@
|
||||
/**
|
||||
* 仪表盘控制器
|
||||
* @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
|
||||
};
|
||||
366
bank-backend/controllers/employeeController.js
Normal file
366
bank-backend/controllers/employeeController.js
Normal file
@@ -0,0 +1,366 @@
|
||||
/**
|
||||
* 员工控制器
|
||||
* @file employeeController.js
|
||||
* @description 处理员工相关的请求
|
||||
*/
|
||||
const { Employee, Department, Position } = require('../models');
|
||||
const { validationResult } = require('express-validator');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 获取员工列表
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getEmployees = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
search = '',
|
||||
department = '',
|
||||
position = '',
|
||||
status = '',
|
||||
sortBy = 'created_at',
|
||||
sortOrder = 'DESC'
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
const whereClause = {};
|
||||
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
whereClause[Op.or] = [
|
||||
{ name: { [Op.like]: `%${search}%` } },
|
||||
{ employee_id: { [Op.like]: `%${search}%` } },
|
||||
{ phone: { [Op.like]: `%${search}%` } },
|
||||
{ email: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
// 部门筛选
|
||||
if (department) {
|
||||
whereClause.department_id = department;
|
||||
}
|
||||
|
||||
// 职位筛选
|
||||
if (position) {
|
||||
whereClause.position_id = position;
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const { count, rows: employees } = await Employee.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
model: Department,
|
||||
as: 'department',
|
||||
attributes: ['id', 'name']
|
||||
},
|
||||
{
|
||||
model: Position,
|
||||
as: 'position',
|
||||
attributes: ['id', 'name', 'level']
|
||||
}
|
||||
],
|
||||
order: [[sortBy, sortOrder.toUpperCase()]],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取员工列表成功',
|
||||
data: {
|
||||
employees,
|
||||
pagination: {
|
||||
current: parseInt(page),
|
||||
pageSize: parseInt(limit),
|
||||
total: count,
|
||||
pages: Math.ceil(count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取员工列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建员工
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.createEmployee = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
name,
|
||||
employee_id,
|
||||
department_id,
|
||||
position_id,
|
||||
phone,
|
||||
email,
|
||||
hire_date,
|
||||
salary,
|
||||
status = 'active'
|
||||
} = req.body;
|
||||
|
||||
// 检查员工编号是否已存在
|
||||
const existingEmployee = await Employee.findOne({
|
||||
where: { employee_id }
|
||||
});
|
||||
|
||||
if (existingEmployee) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '员工编号已存在'
|
||||
});
|
||||
}
|
||||
|
||||
const employee = await Employee.create({
|
||||
name,
|
||||
employee_id,
|
||||
department_id,
|
||||
position_id,
|
||||
phone,
|
||||
email,
|
||||
hire_date,
|
||||
salary: salary * 100, // 转换为分
|
||||
status
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '创建员工成功',
|
||||
data: employee
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建员工错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取员工详情
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getEmployeeById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const employee = await Employee.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: Department,
|
||||
as: 'department',
|
||||
attributes: ['id', 'name', 'description']
|
||||
},
|
||||
{
|
||||
model: Position,
|
||||
as: 'position',
|
||||
attributes: ['id', 'name', 'level', 'description']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!employee) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '员工不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取员工详情成功',
|
||||
data: employee
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取员工详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新员工
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateEmployee = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
// 如果更新薪资,转换为分
|
||||
if (updateData.salary) {
|
||||
updateData.salary = updateData.salary * 100;
|
||||
}
|
||||
|
||||
const employee = await Employee.findByPk(id);
|
||||
|
||||
if (!employee) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '员工不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await employee.update(updateData);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '更新员工成功',
|
||||
data: employee
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新员工错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除员工
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.deleteEmployee = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const employee = await Employee.findByPk(id);
|
||||
|
||||
if (!employee) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '员工不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await employee.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '删除员工成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除员工错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取员工统计
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getEmployeeStats = async (req, res) => {
|
||||
try {
|
||||
const totalEmployees = await Employee.count();
|
||||
const activeEmployees = await Employee.count({ where: { status: 'active' } });
|
||||
const inactiveEmployees = await Employee.count({ where: { status: 'inactive' } });
|
||||
|
||||
const departmentStats = await Employee.findAll({
|
||||
attributes: [
|
||||
'department_id',
|
||||
[Employee.sequelize.fn('COUNT', Employee.sequelize.col('id')), 'count']
|
||||
],
|
||||
include: [{
|
||||
model: Department,
|
||||
as: 'department',
|
||||
attributes: ['name']
|
||||
}],
|
||||
group: ['department_id', 'department.id'],
|
||||
raw: false
|
||||
});
|
||||
|
||||
const positionStats = await Employee.findAll({
|
||||
attributes: [
|
||||
'position_id',
|
||||
[Employee.sequelize.fn('COUNT', Employee.sequelize.col('id')), 'count']
|
||||
],
|
||||
include: [{
|
||||
model: Position,
|
||||
as: 'position',
|
||||
attributes: ['name', 'level']
|
||||
}],
|
||||
group: ['position_id', 'position.id'],
|
||||
raw: false
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取员工统计成功',
|
||||
data: {
|
||||
total: totalEmployees,
|
||||
active: activeEmployees,
|
||||
inactive: inactiveEmployees,
|
||||
departmentStats: departmentStats.map(item => ({
|
||||
department: item.department.name,
|
||||
count: parseInt(item.dataValues.count)
|
||||
})),
|
||||
positionStats: positionStats.map(item => ({
|
||||
position: item.position.name,
|
||||
level: item.position.level,
|
||||
count: parseInt(item.dataValues.count)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取员工统计错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
363
bank-backend/controllers/loanProductController.js
Normal file
363
bank-backend/controllers/loanProductController.js
Normal file
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* 贷款产品控制器
|
||||
* @file loanProductController.js
|
||||
* @description 处理贷款产品相关的请求
|
||||
*/
|
||||
const { LoanProduct } = require('../models');
|
||||
const { validationResult } = require('express-validator');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 获取贷款产品列表
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getLoanProducts = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
search = '',
|
||||
status = '',
|
||||
type = '',
|
||||
sortBy = 'created_at',
|
||||
sortOrder = 'DESC'
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
const whereClause = {};
|
||||
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
whereClause[Op.or] = [
|
||||
{ name: { [Op.like]: `%${search}%` } },
|
||||
{ code: { [Op.like]: `%${search}%` } },
|
||||
{ description: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
// 类型筛选
|
||||
if (type) {
|
||||
whereClause.type = type;
|
||||
}
|
||||
|
||||
const { count, rows: products } = await LoanProduct.findAndCountAll({
|
||||
where: whereClause,
|
||||
order: [[sortBy, sortOrder.toUpperCase()]],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取贷款产品列表成功',
|
||||
data: {
|
||||
products,
|
||||
pagination: {
|
||||
current: parseInt(page),
|
||||
pageSize: parseInt(limit),
|
||||
total: count,
|
||||
pages: Math.ceil(count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取贷款产品列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建贷款产品
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.createLoanProduct = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
name,
|
||||
code,
|
||||
type,
|
||||
description,
|
||||
min_amount,
|
||||
max_amount,
|
||||
interest_rate,
|
||||
term_min,
|
||||
term_max,
|
||||
requirements,
|
||||
status = 'draft'
|
||||
} = req.body;
|
||||
|
||||
// 检查产品代码是否已存在
|
||||
const existingProduct = await LoanProduct.findOne({
|
||||
where: { code }
|
||||
});
|
||||
|
||||
if (existingProduct) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '产品代码已存在'
|
||||
});
|
||||
}
|
||||
|
||||
const product = await LoanProduct.create({
|
||||
name,
|
||||
code,
|
||||
type,
|
||||
description,
|
||||
min_amount: min_amount * 100, // 转换为分
|
||||
max_amount: max_amount * 100,
|
||||
interest_rate,
|
||||
term_min,
|
||||
term_max,
|
||||
requirements,
|
||||
status
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '创建贷款产品成功',
|
||||
data: product
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建贷款产品错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取贷款产品详情
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getLoanProductById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const product = await LoanProduct.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取贷款产品详情成功',
|
||||
data: product
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取贷款产品详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新贷款产品
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateLoanProduct = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
// 如果更新金额,转换为分
|
||||
if (updateData.min_amount) {
|
||||
updateData.min_amount = updateData.min_amount * 100;
|
||||
}
|
||||
if (updateData.max_amount) {
|
||||
updateData.max_amount = updateData.max_amount * 100;
|
||||
}
|
||||
|
||||
const product = await LoanProduct.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await product.update(updateData);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '更新贷款产品成功',
|
||||
data: product
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新贷款产品错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除贷款产品
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.deleteLoanProduct = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const product = await LoanProduct.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await product.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '删除贷款产品成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除贷款产品错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新贷款产品状态
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateLoanProductStatus = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const { status } = req.body;
|
||||
|
||||
const product = await LoanProduct.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await product.update({ status });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '更新贷款产品状态成功',
|
||||
data: product
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新贷款产品状态错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取贷款产品统计
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getLoanProductStats = async (req, res) => {
|
||||
try {
|
||||
const stats = await LoanProduct.findAll({
|
||||
attributes: [
|
||||
'status',
|
||||
[LoanProduct.sequelize.fn('COUNT', LoanProduct.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['status'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
const typeStats = await LoanProduct.findAll({
|
||||
attributes: [
|
||||
'type',
|
||||
[LoanProduct.sequelize.fn('COUNT', LoanProduct.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['type'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取贷款产品统计成功',
|
||||
data: {
|
||||
statusStats: stats,
|
||||
typeStats: typeStats
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取贷款产品统计错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
546
bank-backend/controllers/reportController.js
Normal file
546
bank-backend/controllers/reportController.js
Normal file
@@ -0,0 +1,546 @@
|
||||
const { Transaction, Account, User, Report } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const ExcelJS = require('exceljs');
|
||||
const PDFDocument = require('pdfkit');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
class ReportController {
|
||||
/**
|
||||
* 生成报表
|
||||
*/
|
||||
static async generateReport(req, res) {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '参数验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { reportType, dateRange, transactionType, format } = req.body;
|
||||
const userId = req.user.id;
|
||||
const [startDate, endDate] = dateRange;
|
||||
|
||||
// 创建报表记录
|
||||
const report = await Report.create({
|
||||
name: `${this.getReportTypeName(reportType)}_${new Date().toISOString().split('T')[0]}`,
|
||||
type: reportType,
|
||||
format: format,
|
||||
status: 'processing',
|
||||
createdBy: userId,
|
||||
parameters: {
|
||||
dateRange,
|
||||
transactionType,
|
||||
format
|
||||
}
|
||||
});
|
||||
|
||||
// 异步生成报表
|
||||
setImmediate(async () => {
|
||||
try {
|
||||
const reportData = await this.generateReportData(reportType, startDate, endDate, transactionType);
|
||||
const filePath = await this.exportReport(reportData, format, report.id);
|
||||
|
||||
await report.update({
|
||||
status: 'completed',
|
||||
filePath: filePath,
|
||||
data: reportData
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('报表生成失败:', error);
|
||||
await report.update({
|
||||
status: 'failed',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '报表生成中,请稍后查看',
|
||||
data: {
|
||||
reportId: report.id,
|
||||
status: 'processing'
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('生成报表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '生成报表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取报表历史
|
||||
*/
|
||||
static async getReportHistory(req, res) {
|
||||
try {
|
||||
const { page = 1, pageSize = 10, type, status } = req.query;
|
||||
const offset = (page - 1) * pageSize;
|
||||
|
||||
const where = {};
|
||||
if (type) where.type = type;
|
||||
if (status) where.status = status;
|
||||
|
||||
const { count, rows: reports } = await Report.findAndCountAll({
|
||||
where,
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: parseInt(pageSize),
|
||||
offset: parseInt(offset),
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['username', 'real_name']
|
||||
}]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
reports: reports.map(report => ({
|
||||
id: report.id,
|
||||
name: report.name,
|
||||
type: report.type,
|
||||
format: report.format,
|
||||
status: report.status,
|
||||
createdAt: report.createdAt,
|
||||
createdBy: report.creator ?
|
||||
`${report.creator.real_name || report.creator.username} (${report.creator.role})` :
|
||||
'未知用户',
|
||||
filePath: report.filePath
|
||||
})),
|
||||
pagination: {
|
||||
current: parseInt(page),
|
||||
pageSize: parseInt(pageSize),
|
||||
total: count
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取报表历史失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取报表历史失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载报表
|
||||
*/
|
||||
static async downloadReport(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const report = await Report.findByPk(id);
|
||||
|
||||
if (!report) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '报表不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (report.status !== 'completed') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '报表尚未生成完成'
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = path.join(__dirname, '..', 'uploads', 'reports', report.filePath);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '报表文件不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.download(filePath, `${report.name}.${report.format}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('下载报表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '下载报表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览报表
|
||||
*/
|
||||
static async previewReport(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const report = await Report.findByPk(id);
|
||||
|
||||
if (!report) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '报表不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (report.status !== 'completed') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '报表尚未生成完成'
|
||||
});
|
||||
}
|
||||
|
||||
// 返回报表数据的前100条用于预览
|
||||
const previewData = report.data ? report.data.slice(0, 100) : [];
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
name: report.name,
|
||||
type: report.type,
|
||||
format: report.format,
|
||||
createdAt: report.createdAt,
|
||||
data: previewData
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('预览报表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '预览报表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除报表
|
||||
*/
|
||||
static async deleteReport(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const report = await Report.findByPk(id);
|
||||
|
||||
if (!report) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '报表不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
if (report.filePath) {
|
||||
const filePath = path.join(__dirname, '..', 'uploads', 'reports', report.filePath);
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
await report.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '报表删除成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除报表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除报表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取报表统计
|
||||
*/
|
||||
static async getReportStats(req, res) {
|
||||
try {
|
||||
const stats = await Report.findAll({
|
||||
attributes: [
|
||||
'type',
|
||||
'status',
|
||||
[Report.sequelize.fn('COUNT', Report.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['type', 'status'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
const formattedStats = {
|
||||
total: 0,
|
||||
byType: {},
|
||||
byStatus: {}
|
||||
};
|
||||
|
||||
stats.forEach(stat => {
|
||||
const count = parseInt(stat.count);
|
||||
formattedStats.total += count;
|
||||
|
||||
if (!formattedStats.byType[stat.type]) {
|
||||
formattedStats.byType[stat.type] = 0;
|
||||
}
|
||||
formattedStats.byType[stat.type] += count;
|
||||
|
||||
if (!formattedStats.byStatus[stat.status]) {
|
||||
formattedStats.byStatus[stat.status] = 0;
|
||||
}
|
||||
formattedStats.byStatus[stat.status] += count;
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: formattedStats
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取报表统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取报表统计失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成报表数据
|
||||
*/
|
||||
static async generateReportData(reportType, startDate, endDate, transactionType) {
|
||||
const where = {
|
||||
createdAt: {
|
||||
[Op.between]: [startDate, endDate]
|
||||
}
|
||||
};
|
||||
|
||||
if (transactionType) {
|
||||
where.type = transactionType;
|
||||
}
|
||||
|
||||
switch (reportType) {
|
||||
case 'transaction':
|
||||
return await this.generateTransactionReport(where);
|
||||
case 'account':
|
||||
return await this.generateAccountReport(where);
|
||||
case 'user':
|
||||
return await this.generateUserReport(where);
|
||||
default:
|
||||
throw new Error('不支持的报表类型');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成交易报表数据
|
||||
*/
|
||||
static async generateTransactionReport(where) {
|
||||
const transactions = await Transaction.findAll({
|
||||
where,
|
||||
include: [{
|
||||
model: Account,
|
||||
as: 'account',
|
||||
attributes: ['account_number', 'account_type']
|
||||
}],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
return transactions.map(t => ({
|
||||
id: t.id,
|
||||
date: t.createdAt.toISOString().split('T')[0],
|
||||
type: t.type,
|
||||
amount: t.amount,
|
||||
account: t.account ? t.account.account_number : 'N/A',
|
||||
description: t.description,
|
||||
status: t.status
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成账户报表数据
|
||||
*/
|
||||
static async generateAccountReport(where) {
|
||||
const accounts = await Account.findAll({
|
||||
where: {
|
||||
createdAt: {
|
||||
[Op.between]: [where.createdAt[Op.between][0], where.createdAt[Op.between][1]]
|
||||
}
|
||||
},
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['username', 'real_name']
|
||||
}],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
return accounts.map(a => ({
|
||||
id: a.id,
|
||||
accountNumber: a.account_number,
|
||||
accountType: a.account_type,
|
||||
balance: a.balance,
|
||||
owner: a.user ? a.user.real_name || a.user.username : 'N/A',
|
||||
createdAt: a.createdAt.toISOString().split('T')[0],
|
||||
status: a.status
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成用户报表数据
|
||||
*/
|
||||
static async generateUserReport(where) {
|
||||
const users = await User.findAll({
|
||||
where: {
|
||||
createdAt: {
|
||||
[Op.between]: [where.createdAt[Op.between][0], where.createdAt[Op.between][1]]
|
||||
}
|
||||
},
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
return users.map(u => ({
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
realName: u.real_name,
|
||||
email: u.email,
|
||||
phone: u.phone,
|
||||
role: u.role,
|
||||
status: u.status,
|
||||
createdAt: u.createdAt.toISOString().split('T')[0]
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出报表文件
|
||||
*/
|
||||
static async exportReport(data, format, reportId) {
|
||||
const fileName = `report_${reportId}_${Date.now()}`;
|
||||
const uploadsDir = path.join(__dirname, '..', 'uploads', 'reports');
|
||||
|
||||
// 确保目录存在
|
||||
if (!fs.existsSync(uploadsDir)) {
|
||||
fs.mkdirSync(uploadsDir, { recursive: true });
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case 'excel':
|
||||
return await this.exportToExcel(data, uploadsDir, fileName);
|
||||
case 'pdf':
|
||||
return await this.exportToPDF(data, uploadsDir, fileName);
|
||||
case 'csv':
|
||||
return await this.exportToCSV(data, uploadsDir, fileName);
|
||||
default:
|
||||
throw new Error('不支持的导出格式');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为Excel
|
||||
*/
|
||||
static async exportToExcel(data, uploadsDir, fileName) {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet('报表数据');
|
||||
|
||||
if (data.length > 0) {
|
||||
const headers = Object.keys(data[0]);
|
||||
worksheet.addRow(headers);
|
||||
|
||||
data.forEach(row => {
|
||||
worksheet.addRow(Object.values(row));
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = path.join(uploadsDir, `${fileName}.xlsx`);
|
||||
await workbook.xlsx.writeFile(filePath);
|
||||
return `${fileName}.xlsx`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为PDF
|
||||
*/
|
||||
static async exportToPDF(data, uploadsDir, fileName) {
|
||||
const doc = new PDFDocument();
|
||||
const filePath = path.join(uploadsDir, `${fileName}.pdf`);
|
||||
const stream = fs.createWriteStream(filePath);
|
||||
doc.pipe(stream);
|
||||
|
||||
doc.fontSize(16).text('银行报表', { align: 'center' });
|
||||
doc.moveDown();
|
||||
|
||||
if (data.length > 0) {
|
||||
const headers = Object.keys(data[0]);
|
||||
let tableData = [headers];
|
||||
|
||||
data.forEach(row => {
|
||||
tableData.push(Object.values(row));
|
||||
});
|
||||
|
||||
// 简单的表格实现
|
||||
tableData.forEach((row, index) => {
|
||||
if (index === 0) {
|
||||
doc.fontSize(12).font('Helvetica-Bold');
|
||||
} else {
|
||||
doc.fontSize(10).font('Helvetica');
|
||||
}
|
||||
|
||||
doc.text(row.join(' | '));
|
||||
doc.moveDown(0.5);
|
||||
});
|
||||
}
|
||||
|
||||
doc.end();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on('finish', () => resolve(`${fileName}.pdf`));
|
||||
stream.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为CSV
|
||||
*/
|
||||
static async exportToCSV(data, uploadsDir, fileName) {
|
||||
const filePath = path.join(uploadsDir, `${fileName}.csv`);
|
||||
|
||||
if (data.length === 0) {
|
||||
fs.writeFileSync(filePath, '');
|
||||
return `${fileName}.csv`;
|
||||
}
|
||||
|
||||
const headers = Object.keys(data[0]);
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...data.map(row =>
|
||||
Object.values(row).map(value =>
|
||||
typeof value === 'string' && value.includes(',') ? `"${value}"` : value
|
||||
).join(',')
|
||||
)
|
||||
].join('\n');
|
||||
|
||||
fs.writeFileSync(filePath, csvContent, 'utf8');
|
||||
return `${fileName}.csv`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取报表类型名称
|
||||
*/
|
||||
static getReportTypeName(type) {
|
||||
const names = {
|
||||
transaction: '交易报表',
|
||||
account: '账户报表',
|
||||
user: '用户报表'
|
||||
};
|
||||
return names[type] || type;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReportController;
|
||||
@@ -7,6 +7,7 @@ const { User, Role, Account } = require('../models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { validationResult } = require('express-validator');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
@@ -445,4 +446,278 @@ exports.getUserAccounts = async (req, res) => {
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建用户(管理员)
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.createUser = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { username, email, password, phone, real_name, id_card, role_id } = req.body;
|
||||
|
||||
// 检查用户名是否已存在
|
||||
const existingUser = await User.findOne({
|
||||
where: { username }
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名已存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
const existingEmail = await User.findOne({
|
||||
where: { email }
|
||||
});
|
||||
|
||||
if (existingEmail) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '邮箱已被注册'
|
||||
});
|
||||
}
|
||||
|
||||
// 创建新用户
|
||||
const user = await User.create({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
phone,
|
||||
real_name,
|
||||
id_card,
|
||||
role_id: role_id || 2 // 默认为普通用户
|
||||
});
|
||||
|
||||
// 获取用户信息(包含角色)
|
||||
const userWithRole = await User.findByPk(user.id, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '用户创建成功',
|
||||
data: userWithRole.getSafeInfo()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建用户错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新用户信息(管理员)
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateUser = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { userId } = req.params;
|
||||
const { username, email, phone, real_name, id_card, role_id, status } = req.body;
|
||||
|
||||
const user = await User.findByPk(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户名是否被其他用户使用
|
||||
if (username && username !== user.username) {
|
||||
const existingUser = await User.findOne({
|
||||
where: { username, id: { [Op.ne]: userId } }
|
||||
});
|
||||
if (existingUser) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名已被其他用户使用'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 检查邮箱是否被其他用户使用
|
||||
if (email && email !== user.email) {
|
||||
const existingEmail = await User.findOne({
|
||||
where: { email, id: { [Op.ne]: userId } }
|
||||
});
|
||||
if (existingEmail) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '邮箱已被其他用户使用'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
await user.update({
|
||||
username: username || user.username,
|
||||
email: email || user.email,
|
||||
phone: phone || user.phone,
|
||||
real_name: real_name || user.real_name,
|
||||
id_card: id_card || user.id_card,
|
||||
role_id: role_id || user.role_id,
|
||||
status: status || user.status
|
||||
});
|
||||
|
||||
// 获取更新后的用户信息(包含角色)
|
||||
const updatedUser = await User.findByPk(userId, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户信息更新成功',
|
||||
data: updatedUser.getSafeInfo()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新用户信息错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除用户(管理员)
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.deleteUser = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
|
||||
const user = await User.findByPk(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否是当前用户
|
||||
if (userId === req.user.userId.toString()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '不能删除自己的账户'
|
||||
});
|
||||
}
|
||||
|
||||
// 软删除用户(更新状态为inactive)
|
||||
await user.update({ status: 'inactive' });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除用户错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置用户密码(管理员)
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.resetPassword = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
const { newPassword } = req.body;
|
||||
|
||||
const user = await User.findByPk(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
user.password = newPassword;
|
||||
await user.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '密码重置成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('重置密码错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户详情
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getUserById = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
|
||||
const user = await User.findByPk(userId, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取用户详情成功',
|
||||
data: user.getSafeInfo()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user