Transaction.js 14 KB

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