|
@@ -17,116 +17,72 @@ const createTransactionTable = async () => {
|
|
|
time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
remark VARCHAR(255) DEFAULT NULL COMMENT '备注',
|
|
|
operator_id INT NOT NULL COMMENT '操作人ID',
|
|
|
+ fee_rate DECIMAL(5,2) DEFAULT NULL COMMENT '费率',
|
|
|
+ exchange_rate DECIMAL(10,4) DEFAULT NULL COMMENT '汇率',
|
|
|
INDEX idx_group_time (group_id, time),
|
|
|
FOREIGN KEY (operator_id) REFERENCES users(id)
|
|
|
)
|
|
|
`);
|
|
|
console.log('交易表创建成功');
|
|
|
- } else {
|
|
|
- // 如果表存在,检查是否需要添加新字段
|
|
|
- const [columns] = await pool.query('SHOW COLUMNS FROM transactions');
|
|
|
- const columnNames = columns.map(col => col.Field);
|
|
|
-
|
|
|
- // 检查并添加缺失的字段
|
|
|
- if (!columnNames.includes('operator_id')) {
|
|
|
- try {
|
|
|
- // 先添加字段
|
|
|
- await pool.query(`
|
|
|
- ALTER TABLE transactions
|
|
|
- ADD COLUMN operator_id INT NOT NULL DEFAULT 1 COMMENT '操作人ID' AFTER remark
|
|
|
- `);
|
|
|
- console.log('添加operator_id字段成功');
|
|
|
-
|
|
|
- // 然后添加外键约束
|
|
|
- await pool.query(`
|
|
|
- ALTER TABLE transactions
|
|
|
- ADD CONSTRAINT fk_transaction_operator
|
|
|
- FOREIGN KEY (operator_id) REFERENCES users(id)
|
|
|
- `);
|
|
|
- console.log('添加外键约束成功');
|
|
|
- } catch (error) {
|
|
|
- console.error('添加operator_id字段或外键约束失败:', error);
|
|
|
- // 如果添加外键失败,至少保留字段
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!columnNames.includes('remark')) {
|
|
|
- await pool.query(`
|
|
|
- ALTER TABLE transactions
|
|
|
- ADD COLUMN remark VARCHAR(255) DEFAULT NULL COMMENT '备注' AFTER time
|
|
|
- `);
|
|
|
- console.log('添加remark字段成功');
|
|
|
- }
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- console.error('创建/更新交易表失败:', error);
|
|
|
+ console.error('创建交易表失败:', error);
|
|
|
throw error;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 初始化表
|
|
|
-createTransactionTable().catch(error => {
|
|
|
- console.error('初始化交易表失败:', error);
|
|
|
-});
|
|
|
+createTransactionTable();
|
|
|
|
|
|
-// 交易相关方法
|
|
|
const Transaction = {
|
|
|
// 获取交易列表
|
|
|
findAll: async (query = {}, page = 1, limit = 10) => {
|
|
|
try {
|
|
|
- console.log('查询参数:', query);
|
|
|
+ const offset = (page - 1) * limit;
|
|
|
let sql = `
|
|
|
SELECT
|
|
|
t.*,
|
|
|
- u.username as operator_name
|
|
|
+ u.username as operator_name,
|
|
|
+ g.group_name
|
|
|
FROM transactions t
|
|
|
LEFT JOIN users u ON t.operator_id = u.id
|
|
|
+ LEFT JOIN groups g ON t.group_id = g.group_id
|
|
|
WHERE 1=1
|
|
|
`;
|
|
|
const params = [];
|
|
|
|
|
|
+ if (query.groupId) {
|
|
|
+ sql += ' AND t.group_id = ?';
|
|
|
+ params.push(query.groupId);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (query.type) {
|
|
|
+ sql += ' AND t.type = ?';
|
|
|
+ params.push(query.type);
|
|
|
+ }
|
|
|
+
|
|
|
if (query.startDate) {
|
|
|
sql += ' AND DATE(t.time) >= ?';
|
|
|
params.push(query.startDate);
|
|
|
}
|
|
|
+
|
|
|
if (query.endDate) {
|
|
|
sql += ' AND DATE(t.time) <= ?';
|
|
|
params.push(query.endDate);
|
|
|
}
|
|
|
- if (query.type) {
|
|
|
- sql += ' AND t.type = ?';
|
|
|
- params.push(query.type);
|
|
|
- }
|
|
|
- if (query.groupId) {
|
|
|
- sql += ' AND t.group_id = ?';
|
|
|
- params.push(query.groupId);
|
|
|
- }
|
|
|
-
|
|
|
- // console.log('SQL查询:', sql);
|
|
|
- // console.log('参数:', params);
|
|
|
|
|
|
- // 获取总数
|
|
|
- const countSql = `
|
|
|
- SELECT COUNT(*) as total
|
|
|
- FROM transactions t
|
|
|
- WHERE 1=1
|
|
|
- ${query.startDate ? 'AND DATE(t.time) >= ?' : ''}
|
|
|
- ${query.endDate ? 'AND DATE(t.time) <= ?' : ''}
|
|
|
- ${query.type ? 'AND t.type = ?' : ''}
|
|
|
- ${query.groupId ? 'AND t.group_id = ?' : ''}
|
|
|
- `;
|
|
|
-
|
|
|
- const [countResult] = await pool.query(countSql, params);
|
|
|
- const total = countResult[0]?.total || 0;
|
|
|
- // console.log('查询总数:', total);
|
|
|
+ // 获取总记录数
|
|
|
+ const [countResult] = await pool.query(
|
|
|
+ sql.replace('t.*,', 'COUNT(*) as total,'),
|
|
|
+ params
|
|
|
+ );
|
|
|
+ const total = countResult[0].total;
|
|
|
|
|
|
- // 获取分页数据
|
|
|
+ // 添加排序和分页
|
|
|
sql += ' ORDER BY t.time DESC LIMIT ? OFFSET ?';
|
|
|
- params.push(limit, (page - 1) * limit);
|
|
|
+ params.push(limit, offset);
|
|
|
|
|
|
const [rows] = await pool.query(sql, params);
|
|
|
- // console.log('查询结果数量:', rows.length);
|
|
|
- // console.log('查询结果:', rows);
|
|
|
|
|
|
return {
|
|
|
transactions: rows,
|
|
@@ -142,18 +98,45 @@ const Transaction = {
|
|
|
|
|
|
// 创建交易
|
|
|
create: async (transactionData) => {
|
|
|
- const [result] = await pool.query(
|
|
|
- 'INSERT INTO transactions (group_id, group_name, type, amount, remark, operator_id) VALUES (?, ?, ?, ?, ?, ?)',
|
|
|
- [
|
|
|
- transactionData.groupId,
|
|
|
- transactionData.groupName,
|
|
|
- transactionData.type,
|
|
|
- transactionData.amount,
|
|
|
- transactionData.remark || null,
|
|
|
- transactionData.operatorId
|
|
|
- ]
|
|
|
- );
|
|
|
- return result.insertId;
|
|
|
+ try {
|
|
|
+ // 获取群组的默认费率和汇率
|
|
|
+ const [groupInfo] = await pool.query(
|
|
|
+ 'SELECT in_fee_rate, in_exchange_rate, out_fee_rate, out_exchange_rate FROM groups WHERE group_id = ?',
|
|
|
+ [transactionData.groupId]
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!groupInfo || groupInfo.length === 0) {
|
|
|
+ throw new Error('群组不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据交易类型选择对应的费率和汇率
|
|
|
+ const defaultFeeRate = transactionData.type === 'deposit' ?
|
|
|
+ groupInfo[0].in_fee_rate : groupInfo[0].out_fee_rate;
|
|
|
+ const defaultExchangeRate = transactionData.type === 'deposit' ?
|
|
|
+ groupInfo[0].in_exchange_rate : groupInfo[0].out_exchange_rate;
|
|
|
+
|
|
|
+ // 使用指定的费率和汇率,如果没有指定则使用默认值
|
|
|
+ const feeRate = transactionData.feeRate || defaultFeeRate;
|
|
|
+ const exchangeRate = transactionData.exchangeRate || defaultExchangeRate;
|
|
|
+
|
|
|
+ const [result] = await pool.query(
|
|
|
+ 'INSERT INTO transactions (group_id, group_name, type, amount, remark, operator_id, fee_rate, exchange_rate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
|
|
+ [
|
|
|
+ transactionData.groupId,
|
|
|
+ transactionData.groupName,
|
|
|
+ transactionData.type,
|
|
|
+ transactionData.amount,
|
|
|
+ transactionData.remark || null,
|
|
|
+ transactionData.operatorId,
|
|
|
+ feeRate,
|
|
|
+ exchangeRate
|
|
|
+ ]
|
|
|
+ );
|
|
|
+ return result.insertId;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('创建交易记录失败:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
// 删除交易
|
|
@@ -164,26 +147,29 @@ const Transaction = {
|
|
|
// 获取仪表板数据
|
|
|
getDashboardData: async () => {
|
|
|
try {
|
|
|
- const today = new Date();
|
|
|
- today.setHours(0, 0, 0, 0);
|
|
|
-
|
|
|
// 获取总群组数
|
|
|
- const [totalGroupsResult] = await pool.query('SELECT COUNT(*) as count FROM groups');
|
|
|
- const totalGroups = totalGroupsResult[0].count;
|
|
|
+ const [groupResult] = await pool.query('SELECT COUNT(*) as total FROM groups WHERE is_active = true');
|
|
|
+ const totalGroups = groupResult[0].total;
|
|
|
|
|
|
// 获取总交易数
|
|
|
- const [totalTransactionsResult] = await pool.query('SELECT COUNT(*) as count FROM transactions');
|
|
|
- const totalTransactions = totalTransactionsResult[0].count;
|
|
|
+ const [transactionResult] = await pool.query('SELECT COUNT(*) as total FROM transactions');
|
|
|
+ const totalTransactions = transactionResult[0].total;
|
|
|
|
|
|
// 获取总金额
|
|
|
- const [totalAmountResult] = await pool.query('SELECT SUM(amount) as total FROM transactions');
|
|
|
- const totalAmount = totalAmountResult[0].total || 0;
|
|
|
+ const [amountResult] = await pool.query(`
|
|
|
+ SELECT
|
|
|
+ SUM(CASE WHEN type = 'deposit' THEN amount ELSE -amount END) as total
|
|
|
+ FROM transactions
|
|
|
+ `);
|
|
|
+ const totalAmount = amountResult[0].total || 0;
|
|
|
|
|
|
// 获取今日交易数
|
|
|
- const [todayTransactionsResult] = await pool.query(
|
|
|
- 'SELECT COUNT(*) as count FROM transactions WHERE DATE(time) = CURDATE()'
|
|
|
- );
|
|
|
- const todayTransactions = todayTransactionsResult[0].count;
|
|
|
+ const [todayResult] = await pool.query(`
|
|
|
+ SELECT COUNT(*) as total
|
|
|
+ FROM transactions
|
|
|
+ WHERE DATE(time) = CURDATE()
|
|
|
+ `);
|
|
|
+ const todayTransactions = todayResult[0].total;
|
|
|
|
|
|
// 获取最近交易
|
|
|
const [recentTransactions] = await pool.query(`
|
|
@@ -192,32 +178,25 @@ const Transaction = {
|
|
|
u.username as operator_name
|
|
|
FROM transactions t
|
|
|
LEFT JOIN users u ON t.operator_id = u.id
|
|
|
- ORDER BY t.time DESC
|
|
|
+ ORDER BY t.time DESC
|
|
|
LIMIT 5
|
|
|
`);
|
|
|
|
|
|
// 获取活跃群组
|
|
|
const [activeGroups] = await pool.query(`
|
|
|
SELECT
|
|
|
- g.group_name as name,
|
|
|
- COUNT(t.id) as totalTransactions,
|
|
|
- SUM(CASE WHEN DATE(t.time) = CURDATE() THEN 1 ELSE 0 END) as todayTransactions
|
|
|
+ g.*,
|
|
|
+ COUNT(t.id) as transaction_count,
|
|
|
+ SUM(CASE WHEN t.type = 'deposit' THEN t.amount ELSE 0 END) as total_deposit,
|
|
|
+ SUM(CASE WHEN t.type = 'withdrawal' THEN t.amount ELSE 0 END) as total_withdrawal
|
|
|
FROM groups g
|
|
|
LEFT JOIN transactions t ON g.group_id = t.group_id
|
|
|
- GROUP BY g.id, g.group_name
|
|
|
- ORDER BY todayTransactions DESC, totalTransactions DESC
|
|
|
+ WHERE g.is_active = true
|
|
|
+ GROUP BY g.id
|
|
|
+ ORDER BY transaction_count DESC
|
|
|
LIMIT 5
|
|
|
`);
|
|
|
|
|
|
- console.log('仪表板数据:', {
|
|
|
- totalGroups,
|
|
|
- totalTransactions,
|
|
|
- totalAmount,
|
|
|
- todayTransactions,
|
|
|
- recentTransactions,
|
|
|
- activeGroups
|
|
|
- });
|
|
|
-
|
|
|
return {
|
|
|
totalGroups,
|
|
|
totalTransactions,
|
|
@@ -240,7 +219,9 @@ const Transaction = {
|
|
|
groupName: transactionData.groupName,
|
|
|
type: 'deposit',
|
|
|
amount: parseFloat(transactionData.amount),
|
|
|
- operatorId: transactionData.operatorId || 1 // 添加默认操作者ID
|
|
|
+ operatorId: transactionData.operatorId || 1, // 添加默认操作者ID
|
|
|
+ feeRate: transactionData.feeRate,
|
|
|
+ exchangeRate: transactionData.exchangeRate
|
|
|
});
|
|
|
|
|
|
const transaction = await Transaction.findById(id);
|
|
@@ -273,7 +254,9 @@ const Transaction = {
|
|
|
groupName: transactionData.groupName,
|
|
|
type: 'withdrawal',
|
|
|
amount: parseFloat(transactionData.amount),
|
|
|
- operatorId: transactionData.operatorId || 1 // 添加默认操作者ID
|
|
|
+ operatorId: transactionData.operatorId || 1, // 添加默认操作者ID
|
|
|
+ feeRate: transactionData.feeRate,
|
|
|
+ exchangeRate: transactionData.exchangeRate
|
|
|
});
|
|
|
|
|
|
const transaction = await Transaction.findById(id);
|