436 lines
11 KiB
JavaScript
436 lines
11 KiB
JavaScript
/**
|
|
* 账户控制器
|
|
* @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);
|
|
console.error('错误堆栈:', error.stack);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '服务器内部错误',
|
|
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取账户详情
|
|
* @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}`;
|
|
} |