更新政府端和银行端
This commit is contained in:
434
bank-backend/controllers/accountController.js
Normal file
434
bank-backend/controllers/accountController.js
Normal file
@@ -0,0 +1,434 @@
|
||||
/**
|
||||
* 账户控制器
|
||||
* @file accountController.js
|
||||
* @description 处理银行账户相关的请求
|
||||
*/
|
||||
const { Account, User, Transaction } = require('../models');
|
||||
const { validationResult } = require('express-validator');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 创建账户
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.createAccount = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { user_id, account_type, initial_balance = 0 } = req.body;
|
||||
|
||||
// 检查用户是否存在
|
||||
const user = await User.findByPk(user_id);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 生成账户号码
|
||||
const accountNumber = await generateAccountNumber();
|
||||
|
||||
// 创建账户
|
||||
const account = await Account.create({
|
||||
account_number: accountNumber,
|
||||
user_id,
|
||||
account_type,
|
||||
balance: initial_balance * 100, // 转换为分
|
||||
available_balance: initial_balance * 100,
|
||||
frozen_amount: 0
|
||||
});
|
||||
|
||||
// 如果有初始余额,创建存款交易记录
|
||||
if (initial_balance > 0) {
|
||||
await Transaction.create({
|
||||
transaction_number: await generateTransactionNumber(),
|
||||
account_id: account.id,
|
||||
transaction_type: 'deposit',
|
||||
amount: initial_balance * 100,
|
||||
balance_before: 0,
|
||||
balance_after: initial_balance * 100,
|
||||
description: '开户存款',
|
||||
status: 'completed',
|
||||
processed_at: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '账户创建成功',
|
||||
data: {
|
||||
...account.getSafeInfo(),
|
||||
balance_formatted: account.getBalanceFormatted(),
|
||||
available_balance_formatted: account.getAvailableBalanceFormatted()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建账户错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取账户列表
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getAccounts = async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 10;
|
||||
const offset = (page - 1) * limit;
|
||||
const { user_id, account_type, status } = req.query;
|
||||
|
||||
const whereClause = {};
|
||||
|
||||
// 普通用户只能查看自己的账户
|
||||
if (req.user.role.name !== 'admin') {
|
||||
whereClause.user_id = req.user.id;
|
||||
} else if (user_id) {
|
||||
whereClause.user_id = user_id;
|
||||
}
|
||||
|
||||
if (account_type) {
|
||||
whereClause.account_type = account_type;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const { count, rows } = await Account.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'real_name', 'email']
|
||||
}],
|
||||
limit,
|
||||
offset,
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
accounts: rows.map(account => ({
|
||||
...account.getSafeInfo(),
|
||||
balance_formatted: account.getBalanceFormatted(),
|
||||
available_balance_formatted: account.getAvailableBalanceFormatted(),
|
||||
frozen_amount_formatted: account.getFrozenAmountFormatted()
|
||||
})),
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total: count,
|
||||
pages: Math.ceil(count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取账户列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取账户详情
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getAccountDetail = async (req, res) => {
|
||||
try {
|
||||
const { accountId } = req.params;
|
||||
|
||||
const account = await Account.findByPk(accountId, {
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'real_name', 'email']
|
||||
}]
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '账户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
if (req.user.role.name !== 'admin' && account.user_id !== req.user.id) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '无权访问该账户'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
...account.getSafeInfo(),
|
||||
balance_formatted: account.getBalanceFormatted(),
|
||||
available_balance_formatted: account.getAvailableBalanceFormatted(),
|
||||
frozen_amount_formatted: account.getFrozenAmountFormatted()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取账户详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新账户状态
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateAccountStatus = async (req, res) => {
|
||||
try {
|
||||
const { accountId } = req.params;
|
||||
const { status } = req.body;
|
||||
|
||||
const account = await Account.findByPk(accountId);
|
||||
if (!account) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '账户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await account.update({ status });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '账户状态更新成功',
|
||||
data: account.getSafeInfo()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新账户状态错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 存款
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.deposit = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { accountId } = req.params;
|
||||
const { amount, description } = req.body;
|
||||
|
||||
const account = await Account.findByPk(accountId);
|
||||
if (!account) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '账户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查账户状态
|
||||
if (!account.isActive()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '账户状态异常,无法进行存款操作'
|
||||
});
|
||||
}
|
||||
|
||||
const amountInCents = Math.round(amount * 100);
|
||||
const balanceBefore = account.balance;
|
||||
const balanceAfter = balanceBefore + amountInCents;
|
||||
|
||||
// 开始事务
|
||||
const transaction = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
// 更新账户余额
|
||||
await account.update({
|
||||
balance: balanceAfter,
|
||||
available_balance: account.available_balance + amountInCents
|
||||
}, { transaction });
|
||||
|
||||
// 创建交易记录
|
||||
await Transaction.create({
|
||||
transaction_number: await generateTransactionNumber(),
|
||||
account_id: account.id,
|
||||
transaction_type: 'deposit',
|
||||
amount: amountInCents,
|
||||
balance_before: balanceBefore,
|
||||
balance_after: balanceAfter,
|
||||
description: description || '存款',
|
||||
status: 'completed',
|
||||
processed_at: new Date()
|
||||
}, { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '存款成功',
|
||||
data: {
|
||||
amount: amount,
|
||||
balance_after: account.formatAmount(balanceAfter),
|
||||
transaction_number: await generateTransactionNumber()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('存款错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 取款
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.withdraw = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { accountId } = req.params;
|
||||
const { amount, description } = req.body;
|
||||
|
||||
const account = await Account.findByPk(accountId);
|
||||
if (!account) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '账户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查账户状态
|
||||
if (!account.isActive()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '账户状态异常,无法进行取款操作'
|
||||
});
|
||||
}
|
||||
|
||||
const amountInCents = Math.round(amount * 100);
|
||||
|
||||
// 检查余额是否充足
|
||||
if (!account.hasSufficientBalance(amountInCents)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '账户余额不足'
|
||||
});
|
||||
}
|
||||
|
||||
const balanceBefore = account.balance;
|
||||
const balanceAfter = balanceBefore - amountInCents;
|
||||
|
||||
// 开始事务
|
||||
const transaction = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
// 更新账户余额
|
||||
await account.update({
|
||||
balance: balanceAfter,
|
||||
available_balance: account.available_balance - amountInCents
|
||||
}, { transaction });
|
||||
|
||||
// 创建交易记录
|
||||
await Transaction.create({
|
||||
transaction_number: await generateTransactionNumber(),
|
||||
account_id: account.id,
|
||||
transaction_type: 'withdrawal',
|
||||
amount: amountInCents,
|
||||
balance_before: balanceBefore,
|
||||
balance_after: balanceAfter,
|
||||
description: description || '取款',
|
||||
status: 'completed',
|
||||
processed_at: new Date()
|
||||
}, { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '取款成功',
|
||||
data: {
|
||||
amount: amount,
|
||||
balance_after: account.formatAmount(balanceAfter),
|
||||
transaction_number: await generateTransactionNumber()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('取款错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成账户号码
|
||||
* @returns {String} 账户号码
|
||||
*/
|
||||
async function generateAccountNumber() {
|
||||
const bankCode = process.env.BANK_CODE || '001';
|
||||
const timestamp = Date.now().toString().slice(-8);
|
||||
const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
|
||||
return `${bankCode}${timestamp}${random}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成交易流水号
|
||||
* @returns {String} 交易流水号
|
||||
*/
|
||||
async function generateTransactionNumber() {
|
||||
const timestamp = Date.now().toString();
|
||||
const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
|
||||
return `TXN${timestamp}${random}`;
|
||||
}
|
||||
459
bank-backend/controllers/transactionController.js
Normal file
459
bank-backend/controllers/transactionController.js
Normal file
@@ -0,0 +1,459 @@
|
||||
/**
|
||||
* 交易控制器
|
||||
* @file transactionController.js
|
||||
* @description 处理银行交易相关的请求
|
||||
*/
|
||||
const { Transaction, Account, User } = require('../models');
|
||||
const { validationResult } = require('express-validator');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 获取交易记录列表
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getTransactions = async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 20;
|
||||
const offset = (page - 1) * limit;
|
||||
const {
|
||||
account_id,
|
||||
transaction_type,
|
||||
status,
|
||||
start_date,
|
||||
end_date,
|
||||
amount_min,
|
||||
amount_max
|
||||
} = req.query;
|
||||
|
||||
const whereClause = {};
|
||||
|
||||
// 普通用户只能查看自己账户的交易记录
|
||||
if (req.user.role.name !== 'admin') {
|
||||
const userAccounts = await Account.findAll({
|
||||
where: { user_id: req.user.id },
|
||||
attributes: ['id']
|
||||
});
|
||||
const accountIds = userAccounts.map(account => account.id);
|
||||
whereClause.account_id = { [Op.in]: accountIds };
|
||||
} else if (account_id) {
|
||||
whereClause.account_id = account_id;
|
||||
}
|
||||
|
||||
if (transaction_type) {
|
||||
whereClause.transaction_type = transaction_type;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
if (start_date || end_date) {
|
||||
whereClause.created_at = {};
|
||||
if (start_date) {
|
||||
whereClause.created_at[Op.gte] = new Date(start_date);
|
||||
}
|
||||
if (end_date) {
|
||||
whereClause.created_at[Op.lte] = new Date(end_date);
|
||||
}
|
||||
}
|
||||
|
||||
if (amount_min || amount_max) {
|
||||
whereClause.amount = {};
|
||||
if (amount_min) {
|
||||
whereClause.amount[Op.gte] = Math.round(parseFloat(amount_min) * 100);
|
||||
}
|
||||
if (amount_max) {
|
||||
whereClause.amount[Op.lte] = Math.round(parseFloat(amount_max) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
const { count, rows } = await Transaction.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [{
|
||||
model: Account,
|
||||
as: 'account',
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}]
|
||||
}],
|
||||
limit,
|
||||
offset,
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
transactions: rows.map(transaction => ({
|
||||
...transaction.getSafeInfo(),
|
||||
amount_formatted: transaction.getAmountFormatted(),
|
||||
balance_after_formatted: transaction.getBalanceAfterFormatted(),
|
||||
type_description: transaction.getTypeDescription(),
|
||||
status_description: transaction.getStatusDescription(),
|
||||
is_income: transaction.isIncome(),
|
||||
is_expense: transaction.isExpense()
|
||||
})),
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total: count,
|
||||
pages: Math.ceil(count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取交易记录错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取交易详情
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getTransactionDetail = async (req, res) => {
|
||||
try {
|
||||
const { transactionId } = req.params;
|
||||
|
||||
const transaction = await Transaction.findByPk(transactionId, {
|
||||
include: [{
|
||||
model: Account,
|
||||
as: 'account',
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
if (!transaction) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '交易记录不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
if (req.user.role.name !== 'admin' && transaction.account.user_id !== req.user.id) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '无权访问该交易记录'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
...transaction.getSafeInfo(),
|
||||
amount_formatted: transaction.getAmountFormatted(),
|
||||
balance_after_formatted: transaction.getBalanceAfterFormatted(),
|
||||
type_description: transaction.getTypeDescription(),
|
||||
status_description: transaction.getStatusDescription(),
|
||||
is_income: transaction.isIncome(),
|
||||
is_expense: transaction.isExpense(),
|
||||
can_reverse: transaction.canReverse()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取交易详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 转账
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.transfer = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { from_account_id, to_account_number, amount, description } = req.body;
|
||||
|
||||
// 查找转出账户
|
||||
const fromAccount = await Account.findByPk(from_account_id);
|
||||
if (!fromAccount) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '转出账户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查转出账户权限
|
||||
if (req.user.role.name !== 'admin' && fromAccount.user_id !== req.user.id) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '无权操作该账户'
|
||||
});
|
||||
}
|
||||
|
||||
// 查找转入账户
|
||||
const toAccount = await Account.findOne({
|
||||
where: { account_number: to_account_number },
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}]
|
||||
});
|
||||
|
||||
if (!toAccount) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '转入账户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查账户状态
|
||||
if (!fromAccount.isActive() || !toAccount.isActive()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '账户状态异常,无法进行转账操作'
|
||||
});
|
||||
}
|
||||
|
||||
const amountInCents = Math.round(amount * 100);
|
||||
|
||||
// 检查余额是否充足
|
||||
if (!fromAccount.hasSufficientBalance(amountInCents)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '账户余额不足'
|
||||
});
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
const transaction = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
// 更新转出账户余额
|
||||
await fromAccount.update({
|
||||
balance: fromAccount.balance - amountInCents,
|
||||
available_balance: fromAccount.available_balance - amountInCents
|
||||
}, { transaction });
|
||||
|
||||
// 更新转入账户余额
|
||||
await toAccount.update({
|
||||
balance: toAccount.balance + amountInCents,
|
||||
available_balance: toAccount.available_balance + amountInCents
|
||||
}, { transaction });
|
||||
|
||||
const transactionNumber = await generateTransactionNumber();
|
||||
|
||||
// 创建转出交易记录
|
||||
await Transaction.create({
|
||||
transaction_number: transactionNumber,
|
||||
account_id: fromAccount.id,
|
||||
transaction_type: 'transfer_out',
|
||||
amount: amountInCents,
|
||||
balance_before: fromAccount.balance + amountInCents,
|
||||
balance_after: fromAccount.balance,
|
||||
counterparty_account: toAccount.account_number,
|
||||
counterparty_name: toAccount.user.real_name,
|
||||
description: description || `转账给${toAccount.user.real_name}`,
|
||||
status: 'completed',
|
||||
processed_at: new Date()
|
||||
}, { transaction });
|
||||
|
||||
// 创建转入交易记录
|
||||
await Transaction.create({
|
||||
transaction_number: transactionNumber,
|
||||
account_id: toAccount.id,
|
||||
transaction_type: 'transfer_in',
|
||||
amount: amountInCents,
|
||||
balance_before: toAccount.balance - amountInCents,
|
||||
balance_after: toAccount.balance,
|
||||
counterparty_account: fromAccount.account_number,
|
||||
counterparty_name: fromAccount.user.real_name,
|
||||
description: description || `来自${fromAccount.user.real_name}的转账`,
|
||||
status: 'completed',
|
||||
processed_at: new Date()
|
||||
}, { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '转账成功',
|
||||
data: {
|
||||
transaction_number: transactionNumber,
|
||||
amount: amount,
|
||||
from_account: fromAccount.account_number,
|
||||
to_account: toAccount.account_number,
|
||||
to_account_holder: toAccount.user.real_name
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('转账错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 撤销交易
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.reverseTransaction = async (req, res) => {
|
||||
try {
|
||||
const { transactionId } = req.params;
|
||||
|
||||
const transaction = await Transaction.findByPk(transactionId, {
|
||||
include: [{
|
||||
model: Account,
|
||||
as: 'account'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!transaction) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '交易记录不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
if (req.user.role.name !== 'admin' && transaction.account.user_id !== req.user.id) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '无权操作该交易'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否可以撤销
|
||||
if (!transaction.canReverse()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '该交易无法撤销'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await transaction.reverse();
|
||||
if (result) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: '交易撤销成功'
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: '交易撤销失败'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('撤销交易错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取交易统计
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getTransactionStats = async (req, res) => {
|
||||
try {
|
||||
const { start_date, end_date, account_id } = req.query;
|
||||
|
||||
const whereClause = {};
|
||||
|
||||
// 普通用户只能查看自己账户的统计
|
||||
if (req.user.role.name !== 'admin') {
|
||||
const userAccounts = await Account.findAll({
|
||||
where: { user_id: req.user.id },
|
||||
attributes: ['id']
|
||||
});
|
||||
const accountIds = userAccounts.map(account => account.id);
|
||||
whereClause.account_id = { [Op.in]: accountIds };
|
||||
} else if (account_id) {
|
||||
whereClause.account_id = account_id;
|
||||
}
|
||||
|
||||
if (start_date || end_date) {
|
||||
whereClause.created_at = {};
|
||||
if (start_date) {
|
||||
whereClause.created_at[Op.gte] = new Date(start_date);
|
||||
}
|
||||
if (end_date) {
|
||||
whereClause.created_at[Op.lte] = new Date(end_date);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取交易统计
|
||||
const stats = await Transaction.findAll({
|
||||
where: whereClause,
|
||||
attributes: [
|
||||
'transaction_type',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count'],
|
||||
[sequelize.fn('SUM', sequelize.col('amount')), 'total_amount']
|
||||
],
|
||||
group: ['transaction_type'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 获取总交易数
|
||||
const totalCount = await Transaction.count({ where: whereClause });
|
||||
|
||||
// 获取总交易金额
|
||||
const totalAmount = await Transaction.sum('amount', { where: whereClause });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
total_count: totalCount,
|
||||
total_amount: totalAmount ? (totalAmount / 100).toFixed(2) : '0.00',
|
||||
by_type: stats.map(stat => ({
|
||||
type: stat.transaction_type,
|
||||
count: parseInt(stat.count),
|
||||
total_amount: (parseInt(stat.total_amount) / 100).toFixed(2)
|
||||
}))
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取交易统计错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成交易流水号
|
||||
* @returns {String} 交易流水号
|
||||
*/
|
||||
async function generateTransactionNumber() {
|
||||
const timestamp = Date.now().toString();
|
||||
const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
|
||||
return `TXN${timestamp}${random}`;
|
||||
}
|
||||
448
bank-backend/controllers/userController.js
Normal file
448
bank-backend/controllers/userController.js
Normal file
@@ -0,0 +1,448 @@
|
||||
/**
|
||||
* 用户控制器
|
||||
* @file userController.js
|
||||
* @description 处理用户相关的请求
|
||||
*/
|
||||
const { User, Role, Account } = require('../models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { validationResult } = require('express-validator');
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.register = 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 } = 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 existingIdCard = await User.findOne({
|
||||
where: { id_card }
|
||||
});
|
||||
|
||||
if (existingIdCard) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '身份证号已被注册'
|
||||
});
|
||||
}
|
||||
|
||||
// 创建新用户
|
||||
const user = await User.create({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
phone,
|
||||
real_name,
|
||||
id_card,
|
||||
role_id: 2 // 默认为普通用户
|
||||
});
|
||||
|
||||
// 生成JWT令牌
|
||||
const token = jwt.sign(
|
||||
{ id: user.id, username: user.username },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '注册成功',
|
||||
data: {
|
||||
user: user.getSafeInfo(),
|
||||
token
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('用户注册错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.login = async (req, res) => {
|
||||
try {
|
||||
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: '账户已被禁用'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查账户是否被锁定
|
||||
if (user.locked_until && user.locked_until > new Date()) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '账户已被锁定,请稍后再试'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
const isValidPassword = await user.validPassword(password);
|
||||
if (!isValidPassword) {
|
||||
// 增加登录失败次数
|
||||
user.login_attempts += 1;
|
||||
|
||||
// 如果失败次数达到5次,锁定账户1小时
|
||||
if (user.login_attempts >= 5) {
|
||||
user.locked_until = new Date(Date.now() + 60 * 60 * 1000); // 1小时后解锁
|
||||
}
|
||||
|
||||
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(
|
||||
{ id: user.id, username: user.username },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
user: user.getSafeInfo(),
|
||||
token
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('用户登录错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getProfile = async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.user.id, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: user.getSafeInfo()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户信息错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateProfile = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { phone, real_name, avatar } = req.body;
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
await user.update({
|
||||
phone: phone || user.phone,
|
||||
real_name: real_name || user.real_name,
|
||||
avatar: avatar || user.avatar
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户信息更新成功',
|
||||
data: user.getSafeInfo()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新用户信息错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.changePassword = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { old_password, new_password } = req.body;
|
||||
const user = await User.findByPk(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证旧密码
|
||||
const isValidPassword = await user.validPassword(old_password);
|
||||
if (!isValidPassword) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '原密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
user.password = new_password;
|
||||
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.getUsers = async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 10;
|
||||
const offset = (page - 1) * limit;
|
||||
const search = req.query.search || '';
|
||||
|
||||
const whereClause = {};
|
||||
if (search) {
|
||||
whereClause[Op.or] = [
|
||||
{ username: { [Op.like]: `%${search}%` } },
|
||||
{ email: { [Op.like]: `%${search}%` } },
|
||||
{ real_name: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
const { count, rows } = await User.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}],
|
||||
limit,
|
||||
offset,
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
users: rows.map(user => user.getSafeInfo()),
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total: count,
|
||||
pages: Math.ceil(count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新用户状态(管理员)
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateUserStatus = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
const { status } = req.body;
|
||||
|
||||
const user = await User.findByPk(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await user.update({ status });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户状态更新成功',
|
||||
data: user.getSafeInfo()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新用户状态错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户账户列表
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getUserAccounts = async (req, res) => {
|
||||
try {
|
||||
const userId = req.params.userId || req.user.id;
|
||||
|
||||
// 检查权限:用户只能查看自己的账户,管理员可以查看所有账户
|
||||
if (userId !== req.user.id.toString() && req.user.role.name !== 'admin') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '无权访问该用户的账户信息'
|
||||
});
|
||||
}
|
||||
|
||||
const accounts = await Account.findAll({
|
||||
where: { user_id: userId },
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: accounts.map(account => ({
|
||||
...account.getSafeInfo(),
|
||||
balance_formatted: account.getBalanceFormatted(),
|
||||
available_balance_formatted: account.getAvailableBalanceFormatted(),
|
||||
frozen_amount_formatted: account.getFrozenAmountFormatted()
|
||||
}))
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户账户列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user