修改保险后端代码,政府前端代码

This commit is contained in:
2025-09-22 17:56:30 +08:00
parent 3143c3ad0b
commit 02a25515a9
206 changed files with 35119 additions and 43073 deletions

View File

@@ -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
});
}
};

View 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
};

View 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
};

View 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
});
}
};

View 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
});
}
};

View 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;

View File

@@ -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: '服务器内部错误'
});
}
};