Transaction.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. const { pool } = require('../config/database');
  2. // 创建交易表
  3. const createTransactionTable = async () => {
  4. try {
  5. // 先检查表是否存在
  6. const [tables] = await pool.query('SHOW TABLES LIKE "transactions"');
  7. if (tables.length === 0) {
  8. // 如果表不存在,创建新表
  9. await pool.query(`
  10. CREATE TABLE transactions (
  11. id INT AUTO_INCREMENT PRIMARY KEY,
  12. group_id VARCHAR(50) NOT NULL,
  13. group_name VARCHAR(100) NOT NULL,
  14. type ENUM('deposit', 'withdrawal') NOT NULL COMMENT 'deposit:入款,withdrawal:出款',
  15. amount DECIMAL(10,2) NOT NULL,
  16. time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  17. remark VARCHAR(255) DEFAULT NULL COMMENT '备注',
  18. operator_id VARCHAR(50) NOT NULL COMMENT '操作人ID',
  19. fee_rate DECIMAL(5,2) DEFAULT NULL COMMENT '费率',
  20. exchange_rate DECIMAL(10,4) DEFAULT NULL COMMENT '汇率',
  21. INDEX idx_group_time (group_id, time),
  22. FOREIGN KEY (operator_id) REFERENCES users(id)
  23. )
  24. `);
  25. console.log('交易表创建成功');
  26. }
  27. } catch (error) {
  28. console.error('创建交易表失败:', error);
  29. throw error;
  30. }
  31. };
  32. // 初始化表
  33. createTransactionTable();
  34. const Transaction = {
  35. // 获取交易列表
  36. findAll: async (query = {}, page = 1, limit = 10) => {
  37. try {
  38. const offset = (page - 1) * limit;
  39. let sql = `
  40. SELECT
  41. t.*,
  42. u.username as operator_name,
  43. g.group_name
  44. FROM transactions t
  45. LEFT JOIN users u ON t.operator_id = u.id
  46. LEFT JOIN groups g ON t.group_id = g.group_id
  47. WHERE 1=1
  48. `;
  49. const params = [];
  50. if (query.groupId) {
  51. sql += ' AND t.group_id = ?';
  52. params.push(query.groupId);
  53. }
  54. if (query.type) {
  55. sql += ' AND t.type = ?';
  56. params.push(query.type);
  57. }
  58. if (query.startDate) {
  59. sql += ' AND DATE(t.time) >= ?';
  60. params.push(query.startDate);
  61. }
  62. if (query.endDate) {
  63. sql += ' AND DATE(t.time) <= ?';
  64. params.push(query.endDate);
  65. }
  66. // 获取总记录数
  67. const [countResult] = await pool.query(
  68. sql.replace('t.*,', 'COUNT(*) as total,'),
  69. params
  70. );
  71. const total = countResult[0].total;
  72. // 添加排序和分页
  73. sql += ' ORDER BY t.time DESC LIMIT ? OFFSET ?';
  74. params.push(limit, offset);
  75. const [rows] = await pool.query(sql, params);
  76. return {
  77. transactions: rows,
  78. total,
  79. page: parseInt(page),
  80. pages: Math.ceil(total / limit)
  81. };
  82. } catch (error) {
  83. console.error('查询交易列表失败:', error);
  84. throw error;
  85. }
  86. },
  87. // 创建交易
  88. create: async (transactionData) => {
  89. try {
  90. // 获取群组的默认费率和汇率
  91. const [groupInfo] = await pool.query(
  92. 'SELECT in_fee_rate, in_exchange_rate, out_fee_rate, out_exchange_rate FROM groups WHERE group_id = ?',
  93. [transactionData.groupId]
  94. );
  95. if (!groupInfo || groupInfo.length === 0) {
  96. throw new Error('群组不存在');
  97. }
  98. // 根据交易类型选择对应的费率和汇率
  99. const defaultFeeRate = transactionData.type === 'deposit' ?
  100. groupInfo[0].in_fee_rate : groupInfo[0].out_fee_rate;
  101. const defaultExchangeRate = transactionData.type === 'deposit' ?
  102. groupInfo[0].in_exchange_rate : groupInfo[0].out_exchange_rate;
  103. // 使用指定的费率和汇率,如果没有指定则使用默认值
  104. const feeRate = transactionData.feeRate || defaultFeeRate;
  105. const exchangeRate = transactionData.exchangeRate || defaultExchangeRate;
  106. // 使用群内操作人的ID作为operator_id
  107. const operatorId = transactionData.operatorId || 1;
  108. console.log(operatorId);
  109. const [result] = await pool.query(
  110. 'INSERT INTO transactions (group_id, group_name, type, amount, remark, operator_id, fee_rate, exchange_rate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
  111. [
  112. transactionData.groupId,
  113. transactionData.groupName,
  114. transactionData.type,
  115. transactionData.amount,
  116. transactionData.remark || null,
  117. operatorId, // 使用群内操作人的ID
  118. feeRate,
  119. exchangeRate
  120. ]
  121. );
  122. return result.insertId;
  123. } catch (error) {
  124. console.error('创建交易记录失败:', error);
  125. throw error;
  126. }
  127. },
  128. // 删除交易
  129. delete: async (id) => {
  130. await pool.query('DELETE FROM transactions WHERE id = ?', [id]);
  131. },
  132. // 获取仪表板数据
  133. getDashboardData: async () => {
  134. try {
  135. // 获取总群组数
  136. const [groupResult] = await pool.query('SELECT COUNT(*) as total FROM groups WHERE is_active = true');
  137. const totalGroups = groupResult[0].total;
  138. // 获取总交易数
  139. const [transactionResult] = await pool.query('SELECT COUNT(*) as total FROM transactions');
  140. const totalTransactions = transactionResult[0].total;
  141. // 获取总金额
  142. const [amountResult] = await pool.query(`
  143. SELECT
  144. SUM(CASE WHEN type = 'deposit' THEN amount ELSE -amount END) as total
  145. FROM transactions
  146. `);
  147. const totalAmount = amountResult[0].total || 0;
  148. // 获取今日交易数
  149. const [todayResult] = await pool.query(`
  150. SELECT COUNT(*) as total
  151. FROM transactions
  152. WHERE DATE(time) = CURDATE()
  153. `);
  154. const todayTransactions = todayResult[0].total;
  155. // 获取最近交易
  156. const [recentTransactions] = await pool.query(`
  157. SELECT
  158. t.*,
  159. u.username as operator_name
  160. FROM transactions t
  161. LEFT JOIN users u ON t.operator_id = u.id
  162. ORDER BY t.time DESC
  163. LIMIT 5
  164. `);
  165. // 获取活跃群组
  166. const [activeGroups] = await pool.query(`
  167. SELECT
  168. g.*,
  169. COUNT(t.id) as transaction_count,
  170. SUM(CASE WHEN t.type = 'deposit' THEN t.amount ELSE 0 END) as total_deposit,
  171. SUM(CASE WHEN t.type = 'withdrawal' THEN t.amount ELSE 0 END) as total_withdrawal
  172. FROM groups g
  173. LEFT JOIN transactions t ON g.group_id = t.group_id
  174. WHERE g.is_active = true
  175. GROUP BY g.id
  176. ORDER BY transaction_count DESC
  177. LIMIT 5
  178. `);
  179. return {
  180. totalGroups,
  181. totalTransactions,
  182. totalAmount,
  183. todayTransactions,
  184. recentTransactions,
  185. activeGroups
  186. };
  187. } catch (error) {
  188. console.error('获取仪表板数据失败:', error);
  189. throw error;
  190. }
  191. },
  192. // 入款方法
  193. deposit: async (transactionData) => {
  194. try {
  195. const id = await Transaction.create({
  196. groupId: transactionData.groupId,
  197. groupName: transactionData.groupName,
  198. type: 'deposit',
  199. amount: parseFloat(transactionData.amount),
  200. operatorId: transactionData.operatorId || 1, // 添加默认操作者ID
  201. feeRate: transactionData.feeRate,
  202. exchangeRate: transactionData.exchangeRate
  203. });
  204. const transaction = await Transaction.findById(id);
  205. if (transaction) {
  206. return {
  207. success: true,
  208. transaction,
  209. message: '入款记录创建成功'
  210. };
  211. } else {
  212. return {
  213. success: false,
  214. message: '入款记录创建失败'
  215. };
  216. }
  217. } catch (error) {
  218. console.error('入款记录创建失败:', error);
  219. return {
  220. success: false,
  221. message: '入款记录创建失败,请稍后重试'
  222. };
  223. }
  224. },
  225. // 出款方法
  226. withdrawal: async (transactionData) => {
  227. try {
  228. const id = await Transaction.create({
  229. groupId: transactionData.groupId,
  230. groupName: transactionData.groupName,
  231. type: 'withdrawal',
  232. amount: parseFloat(transactionData.amount),
  233. operatorId: transactionData.operatorId || 1, // 添加默认操作者ID
  234. feeRate: transactionData.feeRate,
  235. exchangeRate: transactionData.exchangeRate
  236. });
  237. const transaction = await Transaction.findById(id);
  238. if (transaction) {
  239. return {
  240. success: true,
  241. transaction,
  242. message: '出款记录创建成功'
  243. };
  244. } else {
  245. return {
  246. success: false,
  247. message: '出款记录创建失败'
  248. };
  249. }
  250. } catch (error) {
  251. console.error('出款记录创建失败:', error);
  252. return {
  253. success: false,
  254. message: '出款记录创建失败,请稍后重试'
  255. };
  256. }
  257. },
  258. // 查询群组账单
  259. getGroupTransactions: async (groupId, options = {}) => {
  260. const {
  261. startDate,
  262. endDate,
  263. type,
  264. page = 1,
  265. limit = 10
  266. } = options;
  267. let sql = `
  268. SELECT
  269. t.*,
  270. u.username as operator_name,
  271. DATE_FORMAT(t.time, '%Y-%m-%d %H:%i:%s') as formatted_time
  272. FROM transactions t
  273. LEFT JOIN users u ON t.operator_id = u.id
  274. WHERE t.group_id = ?
  275. `;
  276. const params = [groupId];
  277. if (startDate) {
  278. sql += ' AND t.time >= ?';
  279. params.push(startDate);
  280. }
  281. if (endDate) {
  282. sql += ' AND t.time <= ?';
  283. params.push(endDate);
  284. }
  285. if (type) {
  286. sql += ' AND t.type = ?';
  287. params.push(type);
  288. }
  289. // 获取总数
  290. const countSql = `
  291. SELECT COUNT(*) as total
  292. FROM transactions t
  293. WHERE 1=1
  294. ${startDate ? 'AND t.time >= ?' : ''}
  295. ${endDate ? 'AND t.time <= ?' : ''}
  296. ${type ? 'AND t.type = ?' : ''}
  297. ${groupId ? 'AND t.group_id = ?' : ''}
  298. `;
  299. const [countResult] = await pool.query(countSql, params);
  300. const total = countResult[0].total;
  301. // 获取分页数据
  302. sql += ' ORDER BY t.time DESC LIMIT ? OFFSET ?';
  303. params.push(limit, (page - 1) * limit);
  304. const [rows] = await pool.query(sql, params);
  305. // 计算总入款和总出款
  306. const [summary] = await pool.query(`
  307. SELECT
  308. SUM(CASE WHEN type = 'deposit' THEN amount ELSE 0 END) as total_deposit,
  309. SUM(CASE WHEN type = 'withdrawal' THEN amount ELSE 0 END) as total_withdrawal
  310. FROM transactions
  311. WHERE group_id = ?
  312. ${startDate ? 'AND time >= ?' : ''}
  313. ${endDate ? 'AND time <= ?' : ''}
  314. `, [groupId, ...(startDate ? [startDate] : []), ...(endDate ? [endDate] : [])]);
  315. return {
  316. transactions: rows,
  317. total,
  318. page: parseInt(page),
  319. pages: Math.ceil(total / limit),
  320. summary: {
  321. totalDeposit: summary[0].total_deposit || 0,
  322. totalWithdrawal: summary[0].total_withdrawal || 0,
  323. balance: (summary[0].total_deposit || 0) - (summary[0].total_withdrawal || 0)
  324. }
  325. };
  326. },
  327. // 根据ID查找交易记录
  328. findById: async (id) => {
  329. try {
  330. const [rows] = await pool.query(`
  331. SELECT
  332. t.*,
  333. u.username as operator_name
  334. FROM transactions t
  335. LEFT JOIN users u ON t.operator_id = u.id
  336. WHERE t.id = ?
  337. `, [id]);
  338. return rows[0] || null;
  339. } catch (error) {
  340. console.error('查询交易记录失败:', error);
  341. return null;
  342. }
  343. }
  344. };
  345. module.exports = Transaction;