From d50290e8fec7b959f333ad89a05e08407805539e Mon Sep 17 00:00:00 2001
From: sunzhuangzhuang <961120009@qq.com>
Date: Wed, 10 Sep 2025 18:10:40 +0800
Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.env | 4 +-
.env.example | 52 +-
.idea/dataSources.xml | 13 +
.idea/sqldialects.xml | 6 +
.idea/vcs.xml | 1 -
balance_audit.js | 347 -----------
balance_monitor.js | 459 --------------
batch_process_commission.js | 72 ---
config/database-init.js | 1118 +---------------------------------
db-monitor.js | 295 ---------
docs/apis/captcha.js | 87 +++
docs/apis/matching.js | 159 +++++
docs/apis/transfers.js | 388 ++++++++++++
docs/apis/user.js | 367 +++++++++++
routes/agents.js | 214 ++++---
routes/agents/agents.js | 1 +
routes/auth.js | 190 +-----
routes/captcha.js | 90 +--
routes/matching.js | 169 +----
routes/sms.js | 61 +-
routes/transfers.js | 451 ++------------
routes/upload.js | 2 +-
routes/users.js | 932 +++++++++++++---------------
services/alipayservice.js | 112 +++-
services/matchingService.js | 147 +++--
services/transferService.js | 146 +++--
services/wechatPayService.js | 55 +-
27 files changed, 2025 insertions(+), 3913 deletions(-)
create mode 100644 .idea/dataSources.xml
create mode 100644 .idea/sqldialects.xml
delete mode 100644 balance_audit.js
delete mode 100644 balance_monitor.js
delete mode 100644 batch_process_commission.js
delete mode 100644 db-monitor.js
create mode 100644 docs/apis/captcha.js
create mode 100644 docs/apis/matching.js
create mode 100644 docs/apis/transfers.js
create mode 100644 docs/apis/user.js
diff --git a/.env b/.env
index eba2d37..7209ee9 100644
--- a/.env
+++ b/.env
@@ -60,5 +60,5 @@ WECHAT_NOTIFY_URL=https://www.zrbjr.com/api/wechat-pay/notify
# 2. 应用私钥和支付宝公钥现在从文件读取
ALIPAY_APP_ID=2021005188682022
ALIPAY_NOTIFY_URL=https://www.zrbjr.com/api/payment/alipay/notify
-ALIPAY_RETURN_URL=https://www.zrbjr.com/payment/success
-ALIPAY_QUIT_URL=https://www.zrbjr.com/payment/cancel
\ No newline at end of file
+ALIPAY_RETURN_URL=https://www.zrbjr.com/payment
+ALIPAY_QUIT_URL=https://www.zrbjr.com/payment/
diff --git a/.env.example b/.env.example
index f84b5d0..eba2d37 100644
--- a/.env.example
+++ b/.env.example
@@ -1,17 +1,19 @@
# 数据库配置
-DB_HOST=localhost
-DB_USER=root
-DB_PASSWORD=your_password
-DB_NAME=your_database
+DB_HOST=114.55.111.44
+DB_USER=maov2
+DB_PASSWORD=5fYhw8z6T62b7heS
+DB_NAME=maov2
# JWT密钥
JWT_SECRET=your_jwt_secret_key
# 阿里云短信服务配置
+# 请在阿里云控制台获取以下配置信息:
+# 1. AccessKey ID 和 AccessKey Secret:在阿里云控制台 -> AccessKey管理中创建
+# 2. 短信签名:在阿里云短信服务控制台中申请并审核通过的签名
+# 3. 短信模板CODE:在阿里云短信服务控制台中申请并审核通过的模板CODE
ALIYUN_ACCESS_KEY_ID=LTAI5tBHymRUu1vvo5tgYpaa
ALIYUN_ACCESS_KEY_SECRET=lNsDZvpUVX2b3pfBQCBawOEyr3dNB9
-
-# 短信模板配置
ALIYUN_SMS_SIGN_NAME=宁波炬融歆创科技
ALIYUN_SMS_TEMPLATE_CODE=SMS_324470054
@@ -20,21 +22,22 @@ NODE_ENV=development
PORT=3000
# 前端地址配置
-FRONTEND_URL=http://localhost:3001
+FRONTEND_URL=https://www.zrbjr.com/frontend
+# FRONTEND_URL=http://114.55.111.44:3001/frontend
# MinIO 对象存储配置
# MinIO服务器地址(不包含协议)
-MINIO_ENDPOINT=localhost
+MINIO_ENDPOINT=114.55.111.44
# MinIO服务器端口
MINIO_PORT=9000
# 是否使用SSL(true/false)
MINIO_USE_SSL=false
# MinIO访问密钥
-MINIO_ACCESS_KEY=minioadmin
+MINIO_ACCESS_KEY=minio
# MinIO秘密密钥
-MINIO_SECRET_KEY=minioadmin
+MINIO_SECRET_KEY=CNy6fMCfyfeaEjbE
# MinIO公开访问地址(用于生成文件URL)
-MINIO_PUBLIC_URL=http://minio.zrbjr.com:9000
+MINIO_PUBLIC_URL=https://minio.zrbjr.com
# MinIO存储桶配置
MINIO_BUCKET_UPLOADS=jurongquan
@@ -42,19 +45,20 @@ MINIO_BUCKET_AVATARS=jurongquan
MINIO_BUCKET_PRODUCTS=jurongquan
MINIO_BUCKET_DOCUMENTS=jurongquan
-# 微信支付配置
-WECHAT_APP_ID=your_wechat_app_id
-WECHAT_MCH_ID=your_wechat_mch_id
-WECHAT_API_KEY=your_wechat_api_key
-WECHAT_API_V3_KEY=your_wechat_api_v3_key
-WECHAT_NOTIFY_URL=https://your-domain.com/api/wechat-pay/notify
+#支付配置
+WECHAT_APP_ID=wx3a702dbe13fd2217
+WECHAT_MCH_ID=1726377336
+WECHAT_API_KEY=NINGBOJURONGkejiyouxiangongsi202
+WECHAT_API_V3_KEY=NINGBOJURONGkejiyouxiangongsi202
WECHAT_CERT_PATH=./cert/apiclient_cert.pem
WECHAT_KEY_PATH=./cert/apiclient_key.pem
+WECHAT_NOTIFY_URL=https://www.zrbjr.com/api/wechat-pay/notify
-# 支付宝支付配置
-ALIPAY_APP_ID=your_alipay_app_id
-ALIPAY_PRIVATE_KEY=your_alipay_private_key
-ALIPAY_PUBLIC_KEY=your_alipay_public_key
-ALIPAY_GATEWAY_URL=https://openapi.alipay.com/gateway.do
-ALIPAY_NOTIFY_URL=https://your-domain.com/api/alipay/notify
-ALIPAY_RETURN_URL=https://your-domain.com/payment/success
+# 支付宝配置
+# 请在支付宝开放平台获取以下配置信息:
+# 1. 应用ID:在支付宝开放平台创建应用后获得
+# 2. 应用私钥和支付宝公钥现在从文件读取
+ALIPAY_APP_ID=2021005188682022
+ALIPAY_NOTIFY_URL=https://www.zrbjr.com/api/payment/alipay/notify
+ALIPAY_RETURN_URL=https://www.zrbjr.com/payment/success
+ALIPAY_QUIT_URL=https://www.zrbjr.com/payment/cancel
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..04e2064
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ mysql.8
+ true
+ 测试服务器1
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://114.55.111.44:3306/test_mao
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
new file mode 100644
index 0000000..56782ca
--- /dev/null
+++ b/.idea/sqldialects.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index e105af4..94a25f7 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,6 +2,5 @@
-
\ No newline at end of file
diff --git a/balance_audit.js b/balance_audit.js
deleted file mode 100644
index e022f9e..0000000
--- a/balance_audit.js
+++ /dev/null
@@ -1,347 +0,0 @@
-const {getDB, initDB} = require('./database');
-const {logger} = require('./config/logger');
-const fs = require('fs');
-const path = require('path');
-
-/**
- * 余额一致性审计工具
- * 用于检查所有用户的余额是否与转账记录一致
- */
-class BalanceAuditor {
- constructor() {
- this.db = null;
- this.auditResults = {
- totalUsers: 0,
- problematicUsers: [],
- summary: {
- totalDiscrepancy: 0,
- usersWithPositiveDiscrepancy: 0,
- usersWithNegativeDiscrepancy: 0,
- maxDiscrepancy: 0,
- minDiscrepancy: 0
- }
- };
- }
-
- /**
- * 初始化数据库连接
- */
- async init() {
- await initDB();
- this.db = getDB();
- console.log('数据库连接已初始化');
- }
-
- /**
- * 计算用户的理论余额
- * @param {number} userId - 用户ID
- * @returns {Object} 包含收入、支出和理论余额的对象
- */
- async calculateUserBalance(userId) {
- // 查询所有已确认和已收到的转账记录
- const [transfers] = await this.db.execute(`
- SELECT
- from_user_id,
- to_user_id,
- amount,
- status,
- transfer_type
- FROM transfers
- WHERE (from_user_id = ? OR to_user_id = ?)
- AND status IN ('confirmed', 'received')
- `, [userId, userId]);
-
- let totalReceived = 0;
- let totalSent = 0;
-
- transfers.forEach(transfer => {
- const amount = parseFloat(transfer.amount);
-
- // 计算收入(作为接收方的转账)
- if (transfer.to_user_id === userId) {
- totalReceived += amount;
- }
-
- // 计算支出(作为发送方的转账)
- if (transfer.from_user_id === userId) {
- totalSent += amount;
- }
- });
-
- return {
- totalReceived,
- totalSent,
- theoreticalBalance: totalReceived - totalSent,
- transferCount: transfers.length
- };
- }
-
- /**
- * 分析用户的问题转账记录
- * @param {number} userId - 用户ID
- * @returns {Object} 问题转账分析结果
- */
- async analyzeProblematicTransfers(userId) {
- // 查询被拒绝、取消的转账
- const [problemTransfers] = await this.db.execute(`
- SELECT
- id,
- from_user_id,
- to_user_id,
- amount,
- status,
- admin_modified_at,
- admin_modified_by,
- admin_note
- FROM transfers
- WHERE (from_user_id = ? OR to_user_id = ?)
- AND status IN ('rejected', 'cancelled', 'not_received')
- ORDER BY admin_modified_at DESC
- `, [userId, userId]);
-
- let shouldBeRefunded = 0;
- const problemDetails = [];
-
- problemTransfers.forEach(transfer => {
- const amount = parseFloat(transfer.amount);
-
- if (transfer.from_user_id === userId) {
- shouldBeRefunded += amount;
- problemDetails.push({
- transferId: transfer.id,
- amount: amount,
- status: transfer.status,
- modifiedAt: transfer.admin_modified_at,
- modifiedBy: transfer.admin_modified_by,
- note: transfer.admin_note
- });
- }
- });
-
- return {
- shouldBeRefunded,
- problemCount: problemDetails.length,
- details: problemDetails
- };
- }
-
- /**
- * 审计单个用户的余额
- * @param {Object} user - 用户对象
- * @returns {Object} 审计结果
- */
- async auditUser(user) {
- const userId = user.id;
- const actualBalance = parseFloat(user.balance);
-
- // 计算理论余额
- const balanceCalc = await this.calculateUserBalance(userId);
-
- // 分析问题转账
- const problemAnalysis = await this.analyzeProblematicTransfers(userId);
-
- // 计算调整后的理论余额(考虑应退还金额)
- const adjustedTheoreticalBalance = balanceCalc.theoreticalBalance + problemAnalysis.shouldBeRefunded;
-
- // 计算差异
- const discrepancy = actualBalance - adjustedTheoreticalBalance;
-
- const result = {
- userId: userId,
- username: user.username,
- realName: user.real_name,
- role: user.role,
- actualBalance: actualBalance,
- theoreticalBalance: balanceCalc.theoreticalBalance,
- adjustedTheoreticalBalance: adjustedTheoreticalBalance,
- discrepancy: discrepancy,
- totalReceived: balanceCalc.totalReceived,
- totalSent: balanceCalc.totalSent,
- transferCount: balanceCalc.transferCount,
- shouldBeRefunded: problemAnalysis.shouldBeRefunded,
- problemTransferCount: problemAnalysis.problemCount,
- problemDetails: problemAnalysis.details,
- isProblematic: Math.abs(discrepancy) > 0.01 // 考虑浮点数精度
- };
-
- return result;
- }
-
- /**
- * 审计所有用户的余额
- */
- async auditAllUsers() {
- console.log('开始审计所有用户余额...');
-
- // 获取所有用户
- const [users] = await this.db.execute(`
- SELECT id, username, real_name, balance, role, created_at
- FROM users
- WHERE role != 'system'
- ORDER BY id
- `);
-
- this.auditResults.totalUsers = users.length;
- console.log(`找到 ${users.length} 个用户需要审计`);
-
- let processedCount = 0;
-
- for (const user of users) {
- try {
- const auditResult = await this.auditUser(user);
-
- if (auditResult.isProblematic) {
- this.auditResults.problematicUsers.push(auditResult);
- console.log(`发现问题用户: ${user.username} (ID: ${user.id}), 差异: ${auditResult.discrepancy.toFixed(2)}`);
- }
-
- // 更新统计信息
- this.updateSummary(auditResult);
-
- processedCount++;
- if (processedCount % 100 === 0) {
- console.log(`已处理 ${processedCount}/${users.length} 个用户`);
- }
-
- } catch (error) {
- console.error(`审计用户 ${user.id} 时出错:`, error.message);
- }
- }
-
- console.log('审计完成!');
- }
-
- /**
- * 更新汇总统计信息
- * @param {Object} auditResult - 单个用户的审计结果
- */
- updateSummary(auditResult) {
- const discrepancy = auditResult.discrepancy;
-
- this.auditResults.summary.totalDiscrepancy += Math.abs(discrepancy);
-
- if (discrepancy > 0) {
- this.auditResults.summary.usersWithPositiveDiscrepancy++;
- } else if (discrepancy < 0) {
- this.auditResults.summary.usersWithNegativeDiscrepancy++;
- }
-
- if (discrepancy > this.auditResults.summary.maxDiscrepancy) {
- this.auditResults.summary.maxDiscrepancy = discrepancy;
- }
-
- if (discrepancy < this.auditResults.summary.minDiscrepancy) {
- this.auditResults.summary.minDiscrepancy = discrepancy;
- }
- }
-
- /**
- * 生成审计报告
- */
- generateReport() {
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
- const reportPath = path.join(__dirname, `balance_audit_report_${timestamp}.json`);
-
- const report = {
- auditTime: new Date().toISOString(),
- summary: {
- totalUsers: this.auditResults.totalUsers,
- problematicUsersCount: this.auditResults.problematicUsers.length,
- totalDiscrepancy: this.auditResults.summary.totalDiscrepancy.toFixed(2),
- usersWithPositiveDiscrepancy: this.auditResults.summary.usersWithPositiveDiscrepancy,
- usersWithNegativeDiscrepancy: this.auditResults.summary.usersWithNegativeDiscrepancy,
- maxDiscrepancy: this.auditResults.summary.maxDiscrepancy.toFixed(2),
- minDiscrepancy: this.auditResults.summary.minDiscrepancy.toFixed(2)
- },
- problematicUsers: this.auditResults.problematicUsers.map(user => ({
- userId: user.userId,
- username: user.username,
- realName: user.realName,
- role: user.role,
- actualBalance: user.actualBalance.toFixed(2),
- theoreticalBalance: user.theoreticalBalance.toFixed(2),
- adjustedTheoreticalBalance: user.adjustedTheoreticalBalance.toFixed(2),
- discrepancy: user.discrepancy.toFixed(2),
- shouldBeRefunded: user.shouldBeRefunded.toFixed(2),
- problemTransferCount: user.problemTransferCount,
- problemDetails: user.problemDetails
- }))
- };
-
- fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf8');
- console.log(`\n审计报告已生成: ${reportPath}`);
-
- return report;
- }
-
- /**
- * 打印控制台报告
- */
- printConsoleReport() {
- console.log('\n=== 余额审计报告 ===');
- console.log(`审计时间: ${new Date().toLocaleString()}`);
- console.log(`总用户数: ${this.auditResults.totalUsers}`);
- console.log(`问题用户数: ${this.auditResults.problematicUsers.length}`);
- console.log(`总差异金额: ${this.auditResults.summary.totalDiscrepancy.toFixed(2)}`);
- console.log(`余额偏高用户数: ${this.auditResults.summary.usersWithPositiveDiscrepancy}`);
- console.log(`余额偏低用户数: ${this.auditResults.summary.usersWithNegativeDiscrepancy}`);
- console.log(`最大差异: ${this.auditResults.summary.maxDiscrepancy.toFixed(2)}`);
- console.log(`最小差异: ${this.auditResults.summary.minDiscrepancy.toFixed(2)}`);
-
- if (this.auditResults.problematicUsers.length > 0) {
- console.log('\n=== 问题用户详情 ===');
- this.auditResults.problematicUsers.forEach((user, index) => {
- console.log(`\n${index + 1}. 用户: ${user.username} (ID: ${user.userId})`);
- console.log(` 实际余额: ${user.actualBalance.toFixed(2)}`);
- console.log(` 理论余额: ${user.theoreticalBalance.toFixed(2)}`);
- console.log(` 调整后理论余额: ${user.adjustedTheoreticalBalance.toFixed(2)}`);
- console.log(` 差异: ${user.discrepancy.toFixed(2)}`);
- console.log(` 应退还金额: ${user.shouldBeRefunded.toFixed(2)}`);
- console.log(` 问题转账数: ${user.problemTransferCount}`);
-
- if (user.problemDetails.length > 0) {
- console.log(' 问题转账详情:');
- user.problemDetails.forEach((detail, i) => {
- console.log(` ${i + 1}. 转账ID: ${detail.transferId}, 金额: ${detail.amount}, 状态: ${detail.status}`);
- });
- }
- });
- }
- }
-
- /**
- * 运行完整的审计流程
- */
- async runAudit() {
- try {
- await this.init();
- await this.auditAllUsers();
- this.printConsoleReport();
- const report = this.generateReport();
-
- // 记录到日志
- logger.info('Balance audit completed', {
- totalUsers: this.auditResults.totalUsers,
- problematicUsers: this.auditResults.problematicUsers.length,
- totalDiscrepancy: this.auditResults.summary.totalDiscrepancy
- });
-
- return report;
-
- } catch (error) {
- console.error('审计过程中发生错误:', error);
- logger.error('Balance audit failed', { error: error.message });
- throw error;
- } finally {
- process.exit(0);
- }
- }
-}
-
-// 如果直接运行此脚本
-if (require.main === module) {
- const auditor = new BalanceAuditor();
- auditor.runAudit().catch(console.error);
-}
-
-module.exports = BalanceAuditor;
\ No newline at end of file
diff --git a/balance_monitor.js b/balance_monitor.js
deleted file mode 100644
index 2396587..0000000
--- a/balance_monitor.js
+++ /dev/null
@@ -1,459 +0,0 @@
-const {getDB, initDB} = require('./database');
-const {logger, auditLogger} = require('./config/logger');
-const BalanceAuditor = require('./balance_audit');
-const cron = require('node-cron');
-const fs = require('fs');
-const path = require('path');
-
-/**
- * 余额监控服务
- * 定期检查用户余额一致性,发现异常时发送警报
- */
-class BalanceMonitor {
- constructor() {
- this.db = null;
- this.auditor = new BalanceAuditor();
- this.alertThreshold = 10; // 差异超过10元时发送警报
- this.maxProblematicUsers = 50; // 最多报告50个问题用户
- }
-
- /**
- * 初始化监控服务
- */
- async init() {
- await initDB();
- this.db = getDB();
- await this.auditor.init(); // 初始化审计器的数据库连接
- console.log('余额监控服务已初始化');
- logger.info('Balance monitor service initialized');
- }
-
- /**
- * 执行快速余额检查
- * 只检查最近有转账活动的用户
- */
- async quickBalanceCheck() {
- try {
- console.log('开始执行快速余额检查...');
-
- // 获取最近7天有转账活动的用户
- const [activeUsers] = await this.db.execute(`
- SELECT DISTINCT u.id, u.username, u.real_name, u.balance, u.role
- FROM users u
- INNER JOIN transfers t ON (u.id = t.from_user_id OR u.id = t.to_user_id)
- WHERE t.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
- AND u.role != 'system'
- ORDER BY u.id
- `);
-
- console.log(`检查 ${activeUsers.length} 个活跃用户的余额`);
-
- const problematicUsers = [];
-
- for (const user of activeUsers) {
- const auditResult = await this.auditor.auditUser(user);
-
- if (auditResult.isProblematic && Math.abs(auditResult.discrepancy) > this.alertThreshold) {
- problematicUsers.push(auditResult);
-
- // 记录警报日志
- auditLogger.warn('Balance discrepancy detected', {
- userId: user.id,
- username: user.username,
- actualBalance: auditResult.actualBalance,
- theoreticalBalance: auditResult.theoreticalBalance,
- discrepancy: auditResult.discrepancy,
- checkTime: new Date().toISOString()
- });
- }
- }
-
- if (problematicUsers.length > 0) {
- await this.sendAlert(problematicUsers, 'quick');
- }
-
- console.log(`快速检查完成,发现 ${problematicUsers.length} 个问题用户`);
-
- } catch (error) {
- console.error('快速余额检查失败:', error);
- logger.error('Quick balance check failed', { error: error.message });
- }
- }
-
- /**
- * 执行完整余额审计
- */
- async fullBalanceAudit() {
- try {
- console.log('开始执行完整余额审计...');
-
- await this.auditor.init();
- await this.auditor.auditAllUsers();
-
- const report = this.auditor.generateReport();
-
- // 如果发现严重问题,发送警报
- if (report.summary.problematicUsersCount > 0) {
- const criticalUsers = this.auditor.auditResults.problematicUsers
- .filter(user => Math.abs(user.discrepancy) > this.alertThreshold)
- .slice(0, this.maxProblematicUsers);
-
- if (criticalUsers.length > 0) {
- await this.sendAlert(criticalUsers, 'full');
- }
- }
-
- console.log('完整审计完成');
-
- } catch (error) {
- console.error('完整余额审计失败:', error);
- logger.error('Full balance audit failed', { error: error.message });
- }
- }
-
- /**
- * 发送余额异常警报
- * @param {Array} problematicUsers - 问题用户列表
- * @param {string} checkType - 检查类型 ('quick' 或 'full')
- */
- async sendAlert(problematicUsers, checkType) {
- const timestamp = new Date().toISOString();
- const alertData = {
- alertTime: timestamp,
- checkType: checkType,
- problematicUsersCount: problematicUsers.length,
- totalDiscrepancy: problematicUsers.reduce((sum, user) => sum + Math.abs(user.discrepancy), 0),
- users: problematicUsers.map(user => ({
- userId: user.userId,
- username: user.username,
- actualBalance: user.actualBalance,
- theoreticalBalance: user.theoreticalBalance,
- discrepancy: user.discrepancy,
- shouldBeRefunded: user.shouldBeRefunded
- }))
- };
-
- // 保存警报到文件
- const alertPath = path.join(__dirname, 'logs', `balance_alert_${timestamp.replace(/[:.]/g, '-')}.json`);
-
- // 确保logs目录存在
- const logsDir = path.join(__dirname, 'logs');
- if (!fs.existsSync(logsDir)) {
- fs.mkdirSync(logsDir, { recursive: true });
- }
-
- fs.writeFileSync(alertPath, JSON.stringify(alertData, null, 2), 'utf8');
-
- // 记录到审计日志
- auditLogger.error('Balance discrepancy alert', {
- checkType: checkType,
- problematicUsersCount: problematicUsers.length,
- totalDiscrepancy: alertData.totalDiscrepancy,
- alertFile: alertPath
- });
-
- console.log(`余额异常警报已生成: ${alertPath}`);
-
- // 这里可以添加其他警报方式,如发送邮件、短信等
- // await this.sendEmailAlert(alertData);
- // await this.sendSMSAlert(alertData);
- }
-
- /**
- * 检查特定用户的余额
- * @param {number} userId - 用户ID
- * @returns {Object} 检查结果
- */
- async checkUserBalance(userId) {
- try {
- const [users] = await this.db.execute(
- 'SELECT id, username, real_name, balance, role FROM users WHERE id = ?',
- [userId]
- );
-
- if (users.length === 0) {
- throw new Error(`用户 ${userId} 不存在`);
- }
-
- const auditResult = await this.auditor.auditUser(users[0]);
-
- // 记录检查日志
- auditLogger.info('Manual balance check', {
- userId: userId,
- username: users[0].username,
- actualBalance: auditResult.actualBalance,
- theoreticalBalance: auditResult.theoreticalBalance,
- discrepancy: auditResult.discrepancy,
- isProblematic: auditResult.isProblematic,
- checkTime: new Date().toISOString()
- });
-
- return auditResult;
-
- } catch (error) {
- logger.error('User balance check failed', { userId, error: error.message });
- throw error;
- }
- }
-
- /**
- * 检查管理员操作的余额一致性
- * 监控最近的管理员状态变更操作,检查是否正确调整了余额
- */
- async checkAdminOperations() {
- try {
- console.log('开始检查管理员操作的余额一致性...');
-
- // 查询最近24小时内管理员修改的转账记录
- const [adminModifiedTransfers] = await this.db.execute(`
- SELECT t.id, t.from_user_id, t.to_user_id, t.amount, t.status,
- t.admin_modified_at, t.admin_modified_by, t.admin_note,
- u_admin.username as admin_username
- FROM transfers t
- LEFT JOIN users u_admin ON t.admin_modified_by = u_admin.id
- WHERE t.admin_modified_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
- AND t.admin_modified_by IS NOT NULL
- ORDER BY t.admin_modified_at DESC
- `);
-
- if (adminModifiedTransfers.length === 0) {
- console.log('最近24小时内没有管理员操作记录');
- return;
- }
-
- console.log(`检查 ${adminModifiedTransfers.length} 个管理员操作记录`);
-
- const suspiciousOperations = [];
-
- for (const transfer of adminModifiedTransfers) {
- // 检查涉及的用户余额是否正常
- const userIds = [transfer.from_user_id, transfer.to_user_id].filter(id => id);
-
- for (const userId of userIds) {
- const [users] = await this.db.execute(
- 'SELECT id, username, balance FROM users WHERE id = ?',
- [userId]
- );
-
- if (users.length > 0) {
- const auditResult = await this.auditor.auditUser(users[0]);
-
- // 如果发现余额异常,且与管理员操作时间相近,标记为可疑
- if (auditResult.isProblematic && Math.abs(auditResult.discrepancy) >= this.alertThreshold) {
- suspiciousOperations.push({
- transferId: transfer.id,
- userId: userId,
- username: users[0].username,
- adminUsername: transfer.admin_username,
- adminModifiedAt: transfer.admin_modified_at,
- adminNote: transfer.admin_note,
- transferStatus: transfer.status,
- transferAmount: transfer.amount,
- balanceDiscrepancy: auditResult.discrepancy,
- actualBalance: auditResult.actualBalance,
- theoreticalBalance: auditResult.theoreticalBalance
- });
- }
- }
- }
- }
-
- if (suspiciousOperations.length > 0) {
- console.log(`⚠️ 发现 ${suspiciousOperations.length} 个可疑的管理员操作`);
-
- // 生成管理员操作警报
- await this.generateAdminOperationAlert(suspiciousOperations);
-
- // 记录警报日志
- logger.warn('Suspicious admin operations detected', {
- count: suspiciousOperations.length,
- operations: suspiciousOperations
- });
- } else {
- console.log('✅ 管理员操作检查正常,未发现余额异常');
- }
-
- } catch (error) {
- console.error('检查管理员操作失败:', error);
- logger.error('Admin operations check failed', { error: error.message });
- }
- }
-
- /**
- * 生成管理员操作警报文件
- * @param {Array} suspiciousOperations - 可疑操作列表
- */
- async generateAdminOperationAlert(suspiciousOperations) {
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
- const alertPath = path.join(__dirname, 'logs', `admin_operation_alert_${timestamp}.json`);
-
- const alertData = {
- alertType: 'admin_operation_balance_discrepancy',
- timestamp: new Date().toISOString(),
- summary: {
- suspiciousOperationsCount: suspiciousOperations.length,
- totalDiscrepancy: suspiciousOperations.reduce((sum, op) => sum + Math.abs(op.balanceDiscrepancy), 0)
- },
- suspiciousOperations: suspiciousOperations,
- recommendations: [
- '检查管理员是否在状态变更时正确设置了adjust_balance参数',
- '验证转账状态变更的合理性',
- '如有必要,手动修复用户余额并记录修复日志'
- ]
- };
-
- // 确保logs目录存在
- const logsDir = path.join(__dirname, 'logs');
- if (!fs.existsSync(logsDir)) {
- fs.mkdirSync(logsDir, { recursive: true });
- }
-
- // 写入警报文件
- fs.writeFileSync(alertPath, JSON.stringify(alertData, null, 2));
-
- console.log(`管理员操作警报已生成: ${alertPath}`);
-
- // 记录警报日志
- auditLogger.warn('Admin operation alert generated', {
- suspiciousOperationsCount: suspiciousOperations.length,
- totalDiscrepancy: alertData.summary.totalDiscrepancy,
- alertFile: alertPath
- });
- }
-
- /**
- * 启动定时监控任务
- */
- startScheduledTasks() {
- console.log('启动定时余额监控任务...');
-
- // 每小时执行一次快速检查
- cron.schedule('0 * * * *', async () => {
- console.log('执行定时快速余额检查');
- await this.quickBalanceCheck();
- });
-
- // 每30分钟检查一次管理员操作
- cron.schedule('*/30 * * * *', async () => {
- console.log('执行管理员操作余额一致性检查');
- await this.checkAdminOperations();
- });
-
- // 每天凌晨2点执行完整审计
- cron.schedule('0 2 * * *', async () => {
- console.log('执行定时完整余额审计');
- await this.fullBalanceAudit();
- });
-
- // 每周日凌晨3点执行深度审计
- cron.schedule('0 3 * * 0', async () => {
- console.log('执行定时深度余额审计');
- await this.fullBalanceAudit();
- });
-
- logger.info('Balance monitoring scheduled tasks started');
- console.log('定时监控任务已启动');
- console.log('- 快速检查: 每小时执行一次');
- console.log('- 管理员操作检查: 每30分钟执行一次');
- console.log('- 完整审计: 每天凌晨2点执行');
- console.log('- 深度审计: 每周日凌晨3点执行');
- }
-
- /**
- * 停止监控服务
- */
- stop() {
- console.log('停止余额监控服务');
- logger.info('Balance monitor service stopped');
- process.exit(0);
- }
-
- /**
- * 运行监控服务
- */
- async run() {
- try {
- await this.init();
-
- // 启动时执行一次快速检查
- await this.quickBalanceCheck();
-
- // 启动时执行一次管理员操作检查
- await this.checkAdminOperations();
-
- // 启动定时任务
- this.startScheduledTasks();
-
- // 监听退出信号
- process.on('SIGINT', () => {
- console.log('\n收到退出信号,正在停止监控服务...');
- this.stop();
- });
-
- process.on('SIGTERM', () => {
- console.log('\n收到终止信号,正在停止监控服务...');
- this.stop();
- });
-
- console.log('余额监控服务正在运行中...');
- console.log('按 Ctrl+C 停止服务');
-
- } catch (error) {
- console.error('启动监控服务失败:', error);
- logger.error('Balance monitor startup failed', { error: error.message });
- process.exit(1);
- }
- }
-}
-
-// 如果直接运行此脚本
-if (require.main === module) {
- const monitor = new BalanceMonitor();
-
- // 检查命令行参数
- const args = process.argv.slice(2);
-
- if (args.includes('--quick')) {
- // 执行一次快速检查后退出
- monitor.init().then(() => {
- return monitor.quickBalanceCheck();
- }).then(() => {
- console.log('快速检查完成');
- process.exit(0);
- }).catch(console.error);
- } else if (args.includes('--full')) {
- // 执行一次完整审计后退出
- monitor.init().then(() => {
- return monitor.fullBalanceAudit();
- }).then(() => {
- console.log('完整审计完成');
- process.exit(0);
- }).catch(console.error);
- } else if (args.includes('--user')) {
- // 检查特定用户
- const userIdIndex = args.indexOf('--user') + 1;
- const userId = args[userIdIndex];
-
- if (!userId) {
- console.error('请指定用户ID: --user ');
- process.exit(1);
- }
-
- monitor.init().then(() => {
- return monitor.checkUserBalance(parseInt(userId));
- }).then((result) => {
- console.log('用户余额检查结果:');
- console.log(`用户: ${result.username} (ID: ${result.userId})`);
- console.log(`实际余额: ${result.actualBalance}`);
- console.log(`理论余额: ${result.theoreticalBalance}`);
- console.log(`差异: ${result.discrepancy}`);
- console.log(`是否有问题: ${result.isProblematic ? '是' : '否'}`);
- process.exit(0);
- }).catch(console.error);
- } else {
- // 启动持续监控服务
- monitor.run().catch(console.error);
- }
-}
-
-module.exports = BalanceMonitor;
\ No newline at end of file
diff --git a/batch_process_commission.js b/batch_process_commission.js
deleted file mode 100644
index cd00e3b..0000000
--- a/batch_process_commission.js
+++ /dev/null
@@ -1,72 +0,0 @@
-const { initDB, getDB } = require('./database');
-const matchingService = require('./services/matchingService');
-
-/**
- * 批量处理所有符合条件的代理佣金
- */
-async function batchProcessCommissions() {
- try {
- console.log('初始化数据库连接...');
- await initDB();
-
- console.log('开始批量处理代理佣金...');
-
- const db = getDB();
-
- // 查找所有有代理关系且转账次数>=3但没有佣金记录的用户
- const [usersNeedCommission] = await db.execute(`
- SELECT
- am.agent_id,
- am.merchant_id,
- u.phone as merchant_phone,
- agent.phone as agent_phone,
- COUNT(t.id) as transfer_count,
- COUNT(acr.id) as commission_count
- FROM agent_merchants am
- JOIN users u ON am.merchant_id = u.id
- JOIN users agent ON am.agent_id = agent.id
- LEFT JOIN transfers t ON am.merchant_id = t.from_user_id AND t.status = 'received'
- LEFT JOIN agent_commission_records acr ON am.agent_id = acr.agent_id
- AND am.merchant_id = acr.merchant_id
- AND acr.description LIKE '%第三次转账%'
- GROUP BY am.agent_id, am.merchant_id
- HAVING transfer_count >= 3 AND commission_count = 0
- `);
-
- if (usersNeedCommission.length === 0) {
- console.log('没有找到需要处理佣金的用户');
- return;
- }
-
- console.log(`找到 ${usersNeedCommission.length} 个用户需要处理佣金:`);
-
- for (const user of usersNeedCommission) {
- console.log(`\n处理用户: ${user.merchant_phone} (ID: ${user.merchant_id})`);
- console.log(` - 代理: ${user.agent_phone} (ID: ${user.agent_id})`);
- console.log(` - 转账次数: ${user.transfer_count}`);
-
- try {
- await matchingService.checkAndProcessAgentCommission(user.merchant_id);
- console.log(` ✅ 佣金处理成功`);
- } catch (error) {
- console.log(` ❌ 佣金处理失败: ${error.message}`);
- }
- }
-
- console.log('\n=== 批量处理完成 ===');
-
- // 检查处理结果
- const [finalCommissions] = await db.execute(
- 'SELECT COUNT(*) as total FROM agent_commission_records WHERE description LIKE "%第三次转账%"'
- );
-
- console.log(`总共生成了 ${finalCommissions[0].total} 条佣金记录`);
-
- } catch (error) {
- console.error('批量处理失败:', error);
- } finally {
- process.exit(0);
- }
-}
-
-batchProcessCommissions();
\ No newline at end of file
diff --git a/config/database-init.js b/config/database-init.js
index 7a7d115..a0481bf 100644
--- a/config/database-init.js
+++ b/config/database-init.js
@@ -8,29 +8,20 @@ const { initDB, getDB, dbConfig } = require('../database');
*/
async function initDatabase() {
try {
- // 首先创建数据库(如果不存在)
- const tempConnection = await mysql.createConnection({
- host: dbConfig.host,
- user: dbConfig.user,
- password: dbConfig.password
- });
-
- // 创建数据库
- await tempConnection.execute(`CREATE DATABASE IF NOT EXISTS ${dbConfig.database} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
- await tempConnection.end();
+
// 初始化数据库连接池
await initDB();
console.log('数据库连接池初始化成功');
// 创建所有表
- await createTables();
+ // await createTables();
// 添加字段(处理表结构升级)
- await addMissingFields();
+ // await addMissingFields();
// 创建默认数据
- await createDefaultData();
+ // await createDefaultData();
console.log('数据库初始化完成');
} catch (error) {
@@ -39,1108 +30,9 @@ async function initDatabase() {
}
}
-/**
- * 创建所有数据库表
- */
-async function createTables() {
- // 用户表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS users (
- id INT AUTO_INCREMENT PRIMARY KEY,
- username VARCHAR(50) UNIQUE NOT NULL,
- phone VARCHAR(20) UNIQUE,
- password VARCHAR(255) NOT NULL,
- role ENUM('user', 'admin') DEFAULT 'user',
- avatar VARCHAR(255),
- points INT DEFAULT 0,
- rongdou INT DEFAULT 0,
- balance DECIMAL(10,2) DEFAULT 0.00,
- real_name VARCHAR(100),
- id_card VARCHAR(18),
- wechat_qr VARCHAR(255),
- alipay_qr VARCHAR(255),
- bank_card VARCHAR(30),
- unionpay_qr VARCHAR(255),
- business_license VARCHAR(500),
- id_card_front VARCHAR(500),
- id_card_back VARCHAR(500),
- audit_status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
- audit_note TEXT,
- audited_by INT,
- audited_at TIMESTAMP NULL,
- city VARCHAR(50),
- district_id INT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (audited_by) REFERENCES users(id),
- FOREIGN KEY (district_id) REFERENCES zhejiang_regions(id)
- )
- `);
-
- // 商品表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS products (
- id INT AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(255) NOT NULL,
- description TEXT,
- price INT NOT NULL,
- points_price INT NOT NULL,
- rongdou_price INT NOT NULL DEFAULT 0,
- original_price INT,
- stock INT DEFAULT 0,
- sales INT DEFAULT 0,
- rating DECIMAL(3,2) DEFAULT 5.00,
- category VARCHAR(100),
- image_url VARCHAR(500),
- images JSON,
- videos JSON,
- details TEXT,
- shop_name VARCHAR(255),
- shop_avatar VARCHAR(500),
- payment_methods JSON,
- status ENUM('active', 'inactive') DEFAULT 'active',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- )
- `);
-
- // 订单表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS orders (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- order_no VARCHAR(50) UNIQUE NOT NULL,
- total_amount INT NOT NULL,
- total_points INT NOT NULL,
- total_rongdou INT NOT NULL DEFAULT 0,
- status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled', 'pre_order') DEFAULT 'pending',
- address JSON,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id)
- )
- `);
- // 创建转账记录表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS transfers (
- id INT AUTO_INCREMENT PRIMARY KEY,
- from_user_id INT NULL,
- to_user_id INT NOT NULL,
- amount DECIMAL(10,2) NOT NULL,
- transfer_type ENUM('initial', 'return', 'user_to_user', 'system_to_user', 'user_to_system') DEFAULT 'user_to_user',
- status ENUM('pending', 'confirmed', 'rejected', 'received', 'not_received') DEFAULT 'pending',
- voucher_url VARCHAR(500),
- description TEXT,
- batch_id VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (from_user_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (to_user_id) REFERENCES users(id) ON DELETE CASCADE
- )
- `);
- // 创建转账确认表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS transfer_confirmations (
- id INT AUTO_INCREMENT PRIMARY KEY,
- transfer_id INT NOT NULL,
- confirmer_id INT NOT NULL,
- action ENUM('confirm', 'reject') NOT NULL,
- note TEXT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (transfer_id) REFERENCES transfers(id) ON DELETE CASCADE,
- FOREIGN KEY (confirmer_id) REFERENCES users(id) ON DELETE CASCADE
- )
- `);
-
- // 订单商品表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS order_items (
- id INT AUTO_INCREMENT PRIMARY KEY,
- order_id INT NOT NULL,
- product_id INT NOT NULL,
- spec_combination_id INT NULL COMMENT '规格组合ID',
- quantity INT NOT NULL,
- price INT NOT NULL,
- points INT NOT NULL,
- points_price INT NOT NULL DEFAULT 0,
- rongdou INT DEFAULT 0 COMMENT '融豆价格',
- rongdou_price INT NOT NULL DEFAULT 0,
- spec_info JSON COMMENT '规格信息快照',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (order_id) REFERENCES orders(id),
- FOREIGN KEY (product_id) REFERENCES products(id),
- FOREIGN KEY (spec_combination_id) REFERENCES product_spec_combinations(id) ON DELETE SET NULL
- )
- `);
-
- // 积分记录表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS points_history (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- type ENUM('earn', 'spend') NOT NULL,
- amount INT NOT NULL,
- description VARCHAR(255),
- order_id INT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id),
- FOREIGN KEY (order_id) REFERENCES orders(id)
- )
- `);
-
- // 融豆记录表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS rongdou_history (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- type ENUM('earn', 'spend') NOT NULL,
- amount INT NOT NULL,
- description VARCHAR(255),
- order_id INT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id),
- FOREIGN KEY (order_id) REFERENCES orders(id)
- )
- `);
-
- // 管理员操作日志表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS admin_operation_logs (
- id INT AUTO_INCREMENT PRIMARY KEY,
- admin_id INT NOT NULL,
- operation_type VARCHAR(50) NOT NULL,
- target_type VARCHAR(50) NOT NULL,
- target_id INT NOT NULL,
- description TEXT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (admin_id) REFERENCES users(id) ON DELETE CASCADE
- )
- `);
-
- // 商品评价表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS product_reviews (
- id INT AUTO_INCREMENT PRIMARY KEY,
- product_id INT NOT NULL,
- user_id INT NOT NULL,
- order_id INT NOT NULL,
- rating INT NOT NULL,
- comment TEXT,
- images JSON,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (product_id) REFERENCES products(id),
- FOREIGN KEY (user_id) REFERENCES users(id),
- FOREIGN KEY (order_id) REFERENCES orders(id)
- )
- `);
-
- // 规格名称表(规格维度)
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS spec_names (
- id INT AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(100) NOT NULL COMMENT '规格名称,如:颜色、尺寸、材质',
- display_name VARCHAR(100) NOT NULL COMMENT '显示名称',
- sort_order INT DEFAULT 0 COMMENT '排序',
- status ENUM('active', 'inactive') DEFAULT 'active',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- UNIQUE KEY unique_name (name)
- )
- `);
-
- // 规格值表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS spec_values (
- id INT AUTO_INCREMENT PRIMARY KEY,
- spec_name_id INT NOT NULL COMMENT '规格名称ID',
- value VARCHAR(100) NOT NULL COMMENT '规格值,如:红色、XL、棉质',
- display_value VARCHAR(100) NOT NULL COMMENT '显示值',
- color_code VARCHAR(20) COMMENT '颜色代码(仅颜色规格使用)',
- image_url VARCHAR(500) COMMENT '规格图片',
- sort_order INT DEFAULT 0 COMMENT '排序',
- status ENUM('active', 'inactive') DEFAULT 'active',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (spec_name_id) REFERENCES spec_names(id) ON DELETE CASCADE,
- UNIQUE KEY unique_spec_value (spec_name_id, value)
- )
- `);
-
- // 商品规格组合表(笛卡尔积结果)
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS product_spec_combinations (
- id INT AUTO_INCREMENT PRIMARY KEY,
- product_id INT NOT NULL COMMENT '商品ID',
- combination_key VARCHAR(255) NOT NULL COMMENT '组合键,如:color_1-size_2-material_3',
- spec_values JSON NOT NULL COMMENT '规格值组合,存储spec_value_id数组',
- price_adjustment INT DEFAULT 0 COMMENT '价格调整',
- points_adjustment INT DEFAULT 0 COMMENT '积分调整',
- rongdou_adjustment INT DEFAULT 0 COMMENT '融豆调整',
- stock INT DEFAULT 0 COMMENT '库存',
- sku_code VARCHAR(100) COMMENT 'SKU编码',
- barcode VARCHAR(100) COMMENT '条形码',
- weight DECIMAL(8,3) COMMENT '重量(kg)',
- volume DECIMAL(10,3) COMMENT '体积(cm³)',
- status ENUM('active', 'inactive') DEFAULT 'active',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
- UNIQUE KEY unique_product_combination (product_id, combination_key),
- INDEX idx_product_status (product_id, status),
- INDEX idx_sku_code (sku_code)
- )
- `);
-
- // 商品规格关联表(定义商品使用哪些规格维度)
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS product_spec_names (
- id INT AUTO_INCREMENT PRIMARY KEY,
- product_id INT NOT NULL COMMENT '商品ID',
- spec_name_id INT NOT NULL COMMENT '规格名称ID',
- is_required BOOLEAN DEFAULT TRUE COMMENT '是否必选规格',
- sort_order INT DEFAULT 0 COMMENT '排序',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
- FOREIGN KEY (spec_name_id) REFERENCES spec_names(id) ON DELETE CASCADE,
- UNIQUE KEY unique_product_spec_name (product_id, spec_name_id)
- )
- `);
-
- // 商品属性表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS product_attributes (
- id INT AUTO_INCREMENT PRIMARY KEY,
- product_id INT NOT NULL,
- attribute_key VARCHAR(100) NOT NULL,
- attribute_value TEXT NOT NULL,
- sort_order INT DEFAULT 0,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
- )
- `);
-
- // 商品收藏表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS product_favorites (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- product_id INT NOT NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
- UNIQUE KEY unique_user_product (user_id, product_id)
- )
- `);
-
- // 购物车表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS cart_items (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- product_id INT NOT NULL,
- quantity INT NOT NULL DEFAULT 1,
- spec_combination_id INT NULL COMMENT '规格组合ID',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
- FOREIGN KEY (spec_combination_id) REFERENCES product_spec_combinations(id) ON DELETE CASCADE,
- UNIQUE KEY unique_user_product_spec (user_id, product_id, spec_combination_id)
- )
- `);
-
- // 用户收货地址表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS user_addresses (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- recipient_name VARCHAR(100) NOT NULL,
- phone VARCHAR(20) NOT NULL,
- province_code VARCHAR(20),
- province_name VARCHAR(50) NOT NULL,
- city_code VARCHAR(20),
- city_name VARCHAR(50) NOT NULL,
- district_code VARCHAR(20),
- district_name VARCHAR(50) NOT NULL,
- detailed_address TEXT NOT NULL,
- postal_code VARCHAR(10),
- label_id INT,
- is_default BOOLEAN DEFAULT FALSE,
- deleted_at TIMESTAMP NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (label_id) REFERENCES address_labels(id) ON DELETE SET NULL
- )
- `);
-
- // 地址标签表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS address_labels (
- id INT AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(50) NOT NULL,
- color VARCHAR(20) DEFAULT '#1890ff',
- user_id INT NULL COMMENT '用户ID,NULL表示系统标签',
- is_system BOOLEAN DEFAULT FALSE,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
- )
- `);
-
- // 全国省市区表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS china_regions (
- id INT AUTO_INCREMENT PRIMARY KEY,
- code VARCHAR(20) NOT NULL UNIQUE,
- name VARCHAR(100) NOT NULL,
- parent_code VARCHAR(20),
- level TINYINT NOT NULL COMMENT '1:省 2:市 3:区',
- sort_order INT DEFAULT 0,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- INDEX idx_parent_code (parent_code),
- INDEX idx_level (level)
- )
- `);
-
- // 匹配订单表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS matching_orders (
- id INT AUTO_INCREMENT PRIMARY KEY,
- initiator_id INT NOT NULL,
- amount DECIMAL(10,2) NOT NULL,
- status ENUM('pending', 'matching', 'completed', 'cancelled') DEFAULT 'pending',
- cycle_count INT DEFAULT 0,
- max_cycles INT DEFAULT 3,
- matching_type ENUM('small', 'large', 'system_reverse') DEFAULT 'small',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (initiator_id) REFERENCES users(id) ON DELETE CASCADE
- )
- `);
-
- // 匹配订单分配表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS order_allocations (
- id INT AUTO_INCREMENT PRIMARY KEY,
- matching_order_id INT NOT NULL,
- from_user_id INT NOT NULL,
- to_user_id INT NOT NULL,
- amount DECIMAL(10,2) NOT NULL,
- cycle_number INT NOT NULL,
- status ENUM('pending', 'confirmed', 'rejected', 'completed') DEFAULT 'pending',
- transfer_id INT,
- outbound_date DATE,
- return_date DATE,
- can_return_after TIMESTAMP,
- confirmed_at TIMESTAMP NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (matching_order_id) REFERENCES matching_orders(id) ON DELETE CASCADE,
- FOREIGN KEY (from_user_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (to_user_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (transfer_id) REFERENCES transfers(id) ON DELETE SET NULL
- )
- `);
-
- // 用户匹配池表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS user_matching_pool (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- available_amount DECIMAL(10,2) DEFAULT 0.00,
- is_active BOOLEAN DEFAULT TRUE,
- last_matched_at TIMESTAMP NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- UNIQUE KEY unique_user (user_id)
- )
- `);
-
- // 匹配记录表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS matching_records (
- id INT AUTO_INCREMENT PRIMARY KEY,
- matching_order_id INT NOT NULL,
- user_id INT NOT NULL,
- action ENUM('join', 'confirm', 'reject', 'complete') NOT NULL,
- amount DECIMAL(10,2),
- note TEXT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (matching_order_id) REFERENCES matching_orders(id) ON DELETE CASCADE,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
- )
- `);
-
- // 创建系统设置表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS system_settings (
- id INT AUTO_INCREMENT PRIMARY KEY,
- setting_key VARCHAR(100) NOT NULL UNIQUE,
- setting_value TEXT,
- description VARCHAR(255),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- )
- `);
-
- // 创建激活码表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS registration_codes (
- id INT AUTO_INCREMENT PRIMARY KEY,
- code VARCHAR(10) UNIQUE NOT NULL,
- expires_at TIMESTAMP NOT NULL,
- is_used BOOLEAN DEFAULT FALSE,
- used_at TIMESTAMP NULL,
- created_by_admin_id INT,
- used_by_user_id INT,
- agent_id INT NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (created_by_admin_id) REFERENCES users(id) ON DELETE SET NULL,
- FOREIGN KEY (used_by_user_id) REFERENCES users(id) ON DELETE SET NULL,
- FOREIGN KEY (agent_id) REFERENCES users(id) ON DELETE SET NULL
- )
- `);
-
- // 创建浙江省区域表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS zhejiang_regions (
- id INT AUTO_INCREMENT PRIMARY KEY,
- city_name VARCHAR(50) NOT NULL,
- district_name VARCHAR(50) NOT NULL,
- region_code VARCHAR(20) UNIQUE NOT NULL,
- is_available BOOLEAN DEFAULT TRUE,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- UNIQUE KEY unique_region (city_name, district_name)
- )
- `);
-
- // 创建区域代理表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS regional_agents (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- region_id INT NOT NULL,
- agent_code VARCHAR(20) UNIQUE NOT NULL,
- status ENUM('pending', 'active', 'suspended', 'terminated') DEFAULT 'pending',
- commission_rate DECIMAL(5,4) DEFAULT 0.1000,
- total_earnings DECIMAL(10,2) DEFAULT 0.00,
- recruited_merchants INT DEFAULT 0,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (region_id) REFERENCES zhejiang_regions(id) ON DELETE CASCADE,
- UNIQUE KEY unique_agent_region (user_id, region_id)
- )
- `);
-
- // 创建代理商户关系表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS agent_merchants (
- id INT AUTO_INCREMENT PRIMARY KEY,
- agent_id INT NOT NULL,
- merchant_id INT NOT NULL,
- registration_code_id INT,
- matching_count INT DEFAULT 0,
- commission_earned DECIMAL(10,2) DEFAULT 0.00,
- is_qualified BOOLEAN DEFAULT FALSE,
- qualified_at TIMESTAMP NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (agent_id) REFERENCES regional_agents(id) ON DELETE CASCADE,
- FOREIGN KEY (merchant_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (registration_code_id) REFERENCES registration_codes(id) ON DELETE SET NULL,
- UNIQUE KEY unique_agent_merchant (agent_id, merchant_id)
- )
- `);
-
- // 创建代理佣金记录表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS agent_commission_records (
- id INT AUTO_INCREMENT PRIMARY KEY,
- agent_id INT NOT NULL,
- merchant_id INT NOT NULL,
- order_id INT,
- commission_amount DECIMAL(10,2) NOT NULL,
- commission_type ENUM('registration', 'matching') DEFAULT 'matching',
- description TEXT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (agent_id) REFERENCES regional_agents(id) ON DELETE CASCADE,
- FOREIGN KEY (merchant_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (order_id) REFERENCES matching_orders(id) ON DELETE SET NULL
- )
- `);
-
- // 通知公告表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS announcements (
- id INT AUTO_INCREMENT PRIMARY KEY,
- title VARCHAR(255) NOT NULL COMMENT '公告标题',
- content TEXT NOT NULL COMMENT '公告内容',
- type ENUM('system', 'maintenance', 'promotion', 'warning') DEFAULT 'system' COMMENT '公告类型',
- priority ENUM('low', 'medium', 'high', 'urgent') DEFAULT 'medium' COMMENT '优先级',
- status ENUM('draft', 'published', 'archived') DEFAULT 'draft' COMMENT '状态',
- is_pinned BOOLEAN DEFAULT FALSE COMMENT '是否置顶',
- publish_time TIMESTAMP NULL COMMENT '发布时间',
- expire_time TIMESTAMP NULL COMMENT '过期时间',
- created_by INT NOT NULL COMMENT '创建者ID',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
- FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE,
- INDEX idx_status (status),
- INDEX idx_type (type),
- INDEX idx_publish_time (publish_time),
- INDEX idx_created_at (created_at)
- ) COMMENT='通知公告表'
- `);
-
- // 用户公告阅读状态表
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS user_announcement_reads (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '用户ID',
- announcement_id INT NOT NULL COMMENT '公告ID',
- is_read BOOLEAN DEFAULT FALSE COMMENT '是否已读',
- read_at TIMESTAMP NULL COMMENT '阅读时间',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
- UNIQUE KEY unique_user_announcement (user_id, announcement_id),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (announcement_id) REFERENCES announcements(id) ON DELETE CASCADE,
- INDEX idx_user_id (user_id),
- INDEX idx_announcement_id (announcement_id),
- INDEX idx_is_read (is_read)
- ) COMMENT='用户公告阅读状态表'
- `);}
-
-/**
- * 添加缺失的字段(处理数据库升级)
- */
-async function addMissingFields() {
- // 为现有的matching_orders表添加字段
- const matchingOrderFields = [
- { name: 'matching_type', sql: 'ALTER TABLE matching_orders ADD COLUMN matching_type ENUM(\'small\', \'large\') DEFAULT \'small\'' },
- { name: 'is_system_reverse', sql: 'ALTER TABLE matching_orders ADD COLUMN is_system_reverse BOOLEAN DEFAULT FALSE' }
- ];
-
- for (const field of matchingOrderFields) {
- try {
- await getDB().execute(field.sql);
- } catch (error) {
- if (!error.message.includes('Duplicate column name')) {
- console.log(`添加${field.name}字段错误:`, error.message);
- }
- }
- }
-
- // 为现有的users表添加字段
- const userFields = [
- { name: 'balance', sql: 'ALTER TABLE users ADD COLUMN balance DECIMAL(10,2) DEFAULT 0.00' },
- { name: 'is_system_account', sql: 'ALTER TABLE users ADD COLUMN is_system_account BOOLEAN DEFAULT FALSE' },
- { name: 'points', sql: 'ALTER TABLE users ADD COLUMN points INT DEFAULT 0' },
- { name: 'avatar', sql: 'ALTER TABLE users ADD COLUMN avatar VARCHAR(255)' },
- { name: 'real_name', sql: 'ALTER TABLE users ADD COLUMN real_name VARCHAR(100)' },
- { name: 'id_card', sql: 'ALTER TABLE users ADD COLUMN id_card VARCHAR(18)' },
- { name: 'wechat_qr', sql: 'ALTER TABLE users ADD COLUMN wechat_qr VARCHAR(255)' },
- { name: 'alipay_qr', sql: 'ALTER TABLE users ADD COLUMN alipay_qr VARCHAR(255)' },
- { name: 'bank_card', sql: 'ALTER TABLE users ADD COLUMN bank_card VARCHAR(30)' },
- { name: 'unionpay_qr', sql: 'ALTER TABLE users ADD COLUMN unionpay_qr VARCHAR(255)' },
- { name: 'phone', sql: 'ALTER TABLE users ADD COLUMN phone VARCHAR(20) UNIQUE' },
- { name: 'completed_withdrawals', sql: 'ALTER TABLE users ADD COLUMN completed_withdrawals INT DEFAULT 0' },
- { name: 'business_license', sql: 'ALTER TABLE users ADD COLUMN business_license VARCHAR(500)' },
- { name: 'id_card_front', sql: 'ALTER TABLE users ADD COLUMN id_card_front VARCHAR(500)' },
- { name: 'id_card_back', sql: 'ALTER TABLE users ADD COLUMN id_card_back VARCHAR(500)' },
- { name: 'audit_status', sql: 'ALTER TABLE users ADD COLUMN audit_status ENUM(\'pending\', \'approved\', \'rejected\') DEFAULT \'pending\'' },
- { name: 'audit_note', sql: 'ALTER TABLE users ADD COLUMN audit_note TEXT' },
- { name: 'audited_by', sql: 'ALTER TABLE users ADD COLUMN audited_by INT' },
- { name: 'audited_at', sql: 'ALTER TABLE users ADD COLUMN audited_at TIMESTAMP NULL' },
- { name: 'city', sql: 'ALTER TABLE users ADD COLUMN city VARCHAR(50)' },
- { name: 'district_id', sql: 'ALTER TABLE users ADD COLUMN district_id INT' }
- ];
-
- for (const field of userFields) {
- try {
- await getDB().execute(field.sql);
- } catch (error) {
- if (!error.message.includes('Duplicate column name')) {
- console.log(`添加用户表${field.name}字段错误:`, error.message);
- }
- }
- }
-
- // 为现有的products表添加字段
- const productFields = [
- { name: 'points_price', sql: 'ALTER TABLE products ADD COLUMN points_price INT NOT NULL DEFAULT 0' },
- { name: 'rongdou_price', sql: 'ALTER TABLE products ADD COLUMN rongdou_price INT NOT NULL DEFAULT 0' },
- { name: 'image_url', sql: 'ALTER TABLE products ADD COLUMN image_url VARCHAR(500)' },
- { name: 'images', sql: 'ALTER TABLE products ADD COLUMN images JSON' },
- { name: 'videos', sql: 'ALTER TABLE products ADD COLUMN videos JSON' },
- { name: 'details', sql: 'ALTER TABLE products ADD COLUMN details TEXT' },
- { name: 'shop_name', sql: 'ALTER TABLE products ADD COLUMN shop_name VARCHAR(255)' },
- { name: 'shop_avatar', sql: 'ALTER TABLE products ADD COLUMN shop_avatar VARCHAR(500)' },
- { name: 'payment_methods', sql: 'ALTER TABLE products ADD COLUMN payment_methods JSON' }
- ];
-
- for (const field of productFields) {
- try {
- await getDB().execute(field.sql);
- } catch (error) {
- if (!error.message.includes('Duplicate column name')) {
- console.log(`添加商品表${field.name}字段错误:`, error.message);
- }
- }
- }
-
- // 为现有的transfers表添加字段
- const transferFields = [
- { name: 'is_overdue', sql: 'ALTER TABLE transfers ADD COLUMN is_overdue BOOLEAN DEFAULT FALSE' },
- { name: 'is_bad_debt', sql: 'ALTER TABLE transfers ADD COLUMN is_bad_debt BOOLEAN DEFAULT FALSE' },
- { name: 'confirmed_at', sql: 'ALTER TABLE transfers ADD COLUMN confirmed_at TIMESTAMP NULL' },
- { name: 'deadline_at', sql: 'ALTER TABLE transfers ADD COLUMN deadline_at TIMESTAMP NULL' },
- { name: 'admin_note', sql: 'ALTER TABLE transfers ADD COLUMN admin_note TEXT' },
- { name: 'admin_modified_at', sql: 'ALTER TABLE transfers ADD COLUMN admin_modified_at TIMESTAMP NULL' },
- { name: 'admin_modified_by', sql: 'ALTER TABLE transfers ADD COLUMN admin_modified_by INT' }
- ];
-
- for (const field of transferFields) {
- try {
- await getDB().execute(field.sql);
- } catch (error) {
- if (!error.message.includes('Duplicate column name')) {
- console.log(`添加转账表${field.name}字段错误:`, error.message);
- }
- }
- }
-
- // 修改transfers表的字段类型
- try {
- await getDB().execute(`
- ALTER TABLE transfers
- MODIFY COLUMN status ENUM('pending', 'confirmed', 'rejected', 'received', 'not_received', 'cancelled') DEFAULT 'pending'
- `);
- } catch (error) {
- console.log('修改transfers状态字段错误:', error.message);
- }
-
- try {
- await getDB().execute(`
- ALTER TABLE transfers
- MODIFY COLUMN from_user_id INT NULL
- `);
- } catch (error) {
- console.log('修改transfers from_user_id字段错误:', error.message);
- }
-
- try {
- await getDB().execute(`
- ALTER TABLE transfers
- MODIFY COLUMN transfer_type ENUM('initial', 'return', 'user_to_user', 'system_to_user', 'user_to_system') DEFAULT 'user_to_user'
- `);
- } catch (error) {
- console.log('修改transfers transfer_type字段错误:', error.message);
- }
-
- // 为现有的order_allocations表添加字段
- const allocationFields = [
- { name: 'confirmed_at', sql: 'ALTER TABLE order_allocations ADD COLUMN confirmed_at TIMESTAMP NULL' },
- { name: 'outbound_date', sql: 'ALTER TABLE order_allocations ADD COLUMN outbound_date DATE' },
- { name: 'return_date', sql: 'ALTER TABLE order_allocations ADD COLUMN return_date DATE' },
- { name: 'can_return_after', sql: 'ALTER TABLE order_allocations ADD COLUMN can_return_after TIMESTAMP' }
- ];
-
- for (const field of allocationFields) {
- try {
- await getDB().execute(field.sql);
- } catch (error) {
- if (!error.message.includes('Duplicate column name')) {
- console.log(`添加分配表${field.name}字段错误:`, error.message);
- }
- }
- }
-
- // 为现有的regional_agents表添加字段
- const agentFields = [
- { name: 'approved_at', sql: 'ALTER TABLE regional_agents ADD COLUMN approved_at TIMESTAMP NULL' },
- { name: 'approved_by_admin_id', sql: 'ALTER TABLE regional_agents ADD COLUMN approved_by_admin_id INT' },
- // 提现相关字段
- { name: 'withdrawn_amount', sql: 'ALTER TABLE regional_agents ADD COLUMN withdrawn_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT "已提现金额"' },
- { name: 'pending_withdrawal', sql: 'ALTER TABLE regional_agents ADD COLUMN pending_withdrawal DECIMAL(10,2) DEFAULT 0.00 COMMENT "待审核提现金额"' },
- { name: 'payment_type', sql: 'ALTER TABLE regional_agents ADD COLUMN payment_type ENUM("bank", "wechat", "alipay", "unionpay") DEFAULT "bank" COMMENT "收款方式类型"' },
- { name: 'bank_name', sql: 'ALTER TABLE regional_agents ADD COLUMN bank_name VARCHAR(100) COMMENT "银行名称"' },
- { name: 'account_number', sql: 'ALTER TABLE regional_agents ADD COLUMN account_number VARCHAR(50) COMMENT "账号/银行账号"' },
- { name: 'account_holder', sql: 'ALTER TABLE regional_agents ADD COLUMN account_holder VARCHAR(100) COMMENT "持有人姓名"' },
- { name: 'qr_code_url', sql: 'ALTER TABLE regional_agents ADD COLUMN qr_code_url VARCHAR(255) COMMENT "收款码图片URL"' },
- { name: 'bank_account', sql: 'ALTER TABLE regional_agents ADD COLUMN bank_account VARCHAR(50) COMMENT "银行账号(兼容旧版本)"' }
- ];
-
- for (const field of agentFields) {
- try {
- await getDB().execute(field.sql);
- } catch (error) {
- if (!error.message.includes('Duplicate column name')) {
- console.log(`添加代理表${field.name}字段错误:`, error.message);
- }
- }
- }
-
- // 为现有的registration_codes表添加字段
- const registrationCodeFields = [
- { name: 'agent_id', sql: 'ALTER TABLE registration_codes ADD COLUMN agent_id INT NULL' }
- ];
-
- for (const field of registrationCodeFields) {
- try {
- await getDB().execute(field.sql);
- } catch (error) {
- if (!error.message.includes('Duplicate column name')) {
- console.log(`添加激活码表${field.name}字段错误:`, error.message);
- }
- }
- }
-
- // 为registration_codes表的agent_id字段添加外键约束
- try {
- await getDB().execute(`
- ALTER TABLE registration_codes
- ADD CONSTRAINT fk_registration_codes_agent_id
- FOREIGN KEY (agent_id) REFERENCES users(id) ON DELETE SET NULL
- `);
- } catch (error) {
- if (!error.message.includes('Duplicate foreign key constraint name')) {
- console.log('添加激活码表agent_id外键约束错误:', error.message);
- }
- }
-
- // 注意:MySQL不支持带WHERE条件的唯一索引
- // 区域激活代理的唯一性通过应用层验证来确保
- // 每个区域只能有一个激活状态的代理,这在代理申请、审核和启用时都会进行验证
-
- // 创建代理提现记录表
- try {
- await getDB().execute(`
- CREATE TABLE IF NOT EXISTS agent_withdrawals (
- id INT AUTO_INCREMENT PRIMARY KEY,
- agent_id INT NOT NULL,
- amount DECIMAL(10,2) NOT NULL,
- payment_type ENUM('bank', 'wechat', 'alipay', 'unionpay') DEFAULT 'bank' COMMENT '收款方式类型',
- bank_name VARCHAR(100) COMMENT '银行名称',
- account_number VARCHAR(50) COMMENT '账号/银行账号',
- account_holder VARCHAR(100) COMMENT '持有人姓名',
- qr_code_url VARCHAR(255) COMMENT '收款码图片URL',
- status ENUM('pending', 'approved', 'rejected', 'completed') DEFAULT 'pending',
- apply_note TEXT,
- admin_note TEXT,
- processed_by INT NULL,
- processed_at TIMESTAMP NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- bank_account VARCHAR(50) COMMENT '银行账号(兼容旧版本)',
- FOREIGN KEY (agent_id) REFERENCES regional_agents(id) ON DELETE CASCADE,
- FOREIGN KEY (processed_by) REFERENCES users(id) ON DELETE SET NULL
- )
- `);
- console.log('代理提现记录表创建成功');
- } catch (error) {
- if (!error.message.includes('Table') || !error.message.includes('already exists')) {
- console.log('创建代理提现记录表错误:', error.message);
- }
- }
-}
-
-/**
- * 创建默认数据
- */
-async function createDefaultData() {
- // 创建默认管理员账号
- const defaultPassword = await bcrypt.hash('admin123', 10);
-
- try {
- await getDB().execute(`
- INSERT IGNORE INTO users (username, phone, password, role)
- VALUES ('admin', '13800000000', ?, 'admin')
- `, [defaultPassword]);
- console.log('默认管理员账号已创建: 用户名: admin, 密码: admin123');
- } catch (error) {
- console.log('默认管理员账号已存在或创建失败:', error.message);
- }
-
- // 创建多个系统公户账号
- const publicPassword = await bcrypt.hash('public123', 10);
- const systemAccounts = [
- { username: 'merchant_001', phone: '13800000001', real_name: '优选商户' },
- { username: 'merchant_002', phone: '13800000002', real_name: '品质商家' },
- { username: 'merchant_003', phone: '13800000003', real_name: '信誉商户' },
- { username: 'merchant_004', phone: '13800000004', real_name: '金牌商家' },
- { username: 'merchant_005', phone: '13800000005', real_name: '钻石商户' }
- ];
-
- for (const account of systemAccounts) {
- try {
- const [result] = await getDB().execute(`
- INSERT IGNORE INTO users (username, phone, password, role, real_name, is_system_account)
- VALUES (?, ?, ?, 'user', ?, TRUE)
- `, [account.username, account.phone, publicPassword, account.real_name]);
-
- if (result.affectedRows > 0) {
- // 为新创建的系统账户设置初始余额
- await getDB().execute(`
- UPDATE users SET balance = 0.00 WHERE id = ?
- `, [result.insertId]);
- console.log(`系统账户已创建: ${account.real_name} (${account.username})`);
- } else {
- // 确保现有系统账户的is_system_account字段正确设置
- await getDB().execute(`
- UPDATE users SET is_system_account = TRUE WHERE username = ?
- `, [account.username]);
- }
- } catch (error) {
- console.log(`系统账户${account.username}已存在或创建失败:`, error.message);
- }
- }
-
- // 初始化浙江省区域数据
- // await initializeZhejiangRegions();
-
- // 初始化默认地址标签
- // await initializeDefaultAddressLabels();
-
- // 初始化全国省市区数据
- // await initializeChinaRegions();
-}
-
-/**
- * 初始化浙江省区域数据
- */
-async function initializeZhejiangRegions() {
- const zhejiangRegions = [
- // 杭州市
- { city: '杭州市', district: '上城区', code: 'HZ_SC' },
- { city: '杭州市', district: '拱墅区', code: 'HZ_GS' },
- { city: '杭州市', district: '西湖区', code: 'HZ_XH' },
- { city: '杭州市', district: '滨江区', code: 'HZ_BJ' },
- { city: '杭州市', district: '萧山区', code: 'HZ_XS' },
- { city: '杭州市', district: '余杭区', code: 'HZ_YH' },
- { city: '杭州市', district: '临平区', code: 'HZ_LP' },
- { city: '杭州市', district: '钱塘区', code: 'HZ_QT' },
- { city: '杭州市', district: '富阳区', code: 'HZ_FY' },
- { city: '杭州市', district: '临安区', code: 'HZ_LA' },
- { city: '杭州市', district: '桐庐县', code: 'HZ_TL' },
- { city: '杭州市', district: '淳安县', code: 'HZ_CA' },
- { city: '杭州市', district: '建德市', code: 'HZ_JD' },
- // 宁波市
- { city: '宁波市', district: '海曙区', code: 'NB_HS' },
- { city: '宁波市', district: '江北区', code: 'NB_JB' },
- { city: '宁波市', district: '北仑区', code: 'NB_BL' },
- { city: '宁波市', district: '镇海区', code: 'NB_ZH' },
- { city: '宁波市', district: '鄞州区', code: 'NB_YZ' },
- { city: '宁波市', district: '奉化区', code: 'NB_FH' },
- { city: '宁波市', district: '象山县', code: 'NB_XS' },
- { city: '宁波市', district: '宁海县', code: 'NB_NH' },
- { city: '宁波市', district: '余姚市', code: 'NB_YY' },
- { city: '宁波市', district: '慈溪市', code: 'NB_CX' },
- // 温州市
- { city: '温州市', district: '鹿城区', code: 'WZ_LC' },
- { city: '温州市', district: '龙湾区', code: 'WZ_LW' },
- { city: '温州市', district: '瓯海区', code: 'WZ_OH' },
- { city: '温州市', district: '洞头区', code: 'WZ_DT' },
- { city: '温州市', district: '永嘉县', code: 'WZ_YJ' },
- { city: '温州市', district: '平阳县', code: 'WZ_PY' },
- { city: '温州市', district: '苍南县', code: 'WZ_CN' },
- { city: '温州市', district: '文成县', code: 'WZ_WC' },
- { city: '温州市', district: '泰顺县', code: 'WZ_TS' },
- { city: '温州市', district: '瑞安市', code: 'WZ_RA' },
- { city: '温州市', district: '乐清市', code: 'WZ_LQ' },
- // 嘉兴市
- { city: '嘉兴市', district: '南湖区', code: 'JX_NH' },
- { city: '嘉兴市', district: '秀洲区', code: 'JX_XZ' },
- { city: '嘉兴市', district: '嘉善县', code: 'JX_JS' },
- { city: '嘉兴市', district: '海盐县', code: 'JX_HY' },
- { city: '嘉兴市', district: '海宁市', code: 'JX_HN' },
- { city: '嘉兴市', district: '平湖市', code: 'JX_PH' },
- { city: '嘉兴市', district: '桐乡市', code: 'JX_TX' },
- // 湖州市
- { city: '湖州市', district: '吴兴区', code: 'HuZ_WX' },
- { city: '湖州市', district: '南浔区', code: 'HuZ_NX' },
- { city: '湖州市', district: '德清县', code: 'HuZ_DQ' },
- { city: '湖州市', district: '长兴县', code: 'HuZ_CX' },
- { city: '湖州市', district: '安吉县', code: 'HuZ_AJ' },
- // 绍兴市
- { city: '绍兴市', district: '越城区', code: 'SX_YC' },
- { city: '绍兴市', district: '柯桥区', code: 'SX_KQ' },
- { city: '绍兴市', district: '上虞区', code: 'SX_SY' },
- { city: '绍兴市', district: '新昌县', code: 'SX_XC' },
- { city: '绍兴市', district: '诸暨市', code: 'SX_ZJ' },
- { city: '绍兴市', district: '嵊州市', code: 'SX_SZ' },
- // 金华市
- { city: '金华市', district: '婺城区', code: 'JH_WC' },
- { city: '金华市', district: '金东区', code: 'JH_JD' },
- { city: '金华市', district: '武义县', code: 'JH_WY' },
- { city: '金华市', district: '浦江县', code: 'JH_PJ' },
- { city: '金华市', district: '磐安县', code: 'JH_PA' },
- { city: '金华市', district: '兰溪市', code: 'JH_LX' },
- { city: '金华市', district: '义乌市', code: 'JH_YW' },
- { city: '金华市', district: '东阳市', code: 'JH_DY' },
- { city: '金华市', district: '永康市', code: 'JH_YK' },
- // 衢州市
- { city: '衢州市', district: '柯城区', code: 'QZ_KC' },
- { city: '衢州市', district: '衢江区', code: 'QZ_QJ' },
- { city: '衢州市', district: '常山县', code: 'QZ_CS' },
- { city: '衢州市', district: '开化县', code: 'QZ_KH' },
- { city: '衢州市', district: '龙游县', code: 'QZ_LY' },
- { city: '衢州市', district: '江山市', code: 'QZ_JS' },
- // 舟山市
- { city: '舟山市', district: '定海区', code: 'ZS_DH' },
- { city: '舟山市', district: '普陀区', code: 'ZS_PT' },
- { city: '舟山市', district: '岱山县', code: 'ZS_DS' },
- { city: '舟山市', district: '嵊泗县', code: 'ZS_SS' },
- // 台州市
- { city: '台州市', district: '椒江区', code: 'TZ_JJ' },
- { city: '台州市', district: '黄岩区', code: 'TZ_HY' },
- { city: '台州市', district: '路桥区', code: 'TZ_LQ' },
- { city: '台州市', district: '三门县', code: 'TZ_SM' },
- { city: '台州市', district: '天台县', code: 'TZ_TT' },
- { city: '台州市', district: '仙居县', code: 'TZ_XJ' },
- { city: '台州市', district: '温岭市', code: 'TZ_WL' },
- { city: '台州市', district: '临海市', code: 'TZ_LH' },
- { city: '台州市', district: '玉环市', code: 'TZ_YH' },
- // 丽水市
- { city: '丽水市', district: '莲都区', code: 'LS_LD' },
- { city: '丽水市', district: '青田县', code: 'LS_QT' },
- { city: '丽水市', district: '缙云县', code: 'LS_JY' },
- { city: '丽水市', district: '遂昌县', code: 'LS_SC' },
- { city: '丽水市', district: '松阳县', code: 'LS_SY' },
- { city: '丽水市', district: '云和县', code: 'LS_YH' },
- { city: '丽水市', district: '庆元县', code: 'LS_QY' },
- { city: '丽水市', district: '景宁县', code: 'LS_JN' },
- { city: '丽水市', district: '龙泉市', code: 'LS_LQ' }
- ];
-
- // 批量插入区域数据
- for (const region of zhejiangRegions) {
- try {
- await getDB().execute(
- 'INSERT IGNORE INTO zhejiang_regions (city_name, district_name, region_code) VALUES (?, ?, ?)',
- [region.city, region.district, region.code]
- );
- } catch (error) {
- console.log(`插入区域数据失败: ${region.city} ${region.district}`, error.message);
- }
- }
-}
-
-/**
- * 初始化默认地址标签
- */
-async function initializeDefaultAddressLabels() {
- const defaultLabels = [
- { name: '家', color: '#52c41a' },
- { name: '公司', color: '#1890ff' },
- { name: '学校', color: '#722ed1' }
- ];
-
- for (const label of defaultLabels) {
- try {
- await getDB().execute(`
- INSERT IGNORE INTO address_labels (name, color, user_id, is_system)
- VALUES (?, ?, NULL, TRUE)
- `, [label.name, label.color]);
- } catch (error) {
- console.log(`默认标签${label.name}创建失败:`, error.message);
- }
- }
- console.log('默认地址标签初始化完成');
-}
-
-/**
- * 初始化全国省市区数据
- */
-async function initializeChinaRegions() {
- const regions = [
- // 省级
- { code: '110000', name: '北京市', parent_code: null, level: 1 },
- { code: '120000', name: '天津市', parent_code: null, level: 1 },
- { code: '130000', name: '河北省', parent_code: null, level: 1 },
- { code: '140000', name: '山西省', parent_code: null, level: 1 },
- { code: '150000', name: '内蒙古自治区', parent_code: null, level: 1 },
- { code: '210000', name: '辽宁省', parent_code: null, level: 1 },
- { code: '220000', name: '吉林省', parent_code: null, level: 1 },
- { code: '230000', name: '黑龙江省', parent_code: null, level: 1 },
- { code: '310000', name: '上海市', parent_code: null, level: 1 },
- { code: '320000', name: '江苏省', parent_code: null, level: 1 },
- { code: '330000', name: '浙江省', parent_code: null, level: 1 },
- { code: '340000', name: '安徽省', parent_code: null, level: 1 },
- { code: '350000', name: '福建省', parent_code: null, level: 1 },
- { code: '360000', name: '江西省', parent_code: null, level: 1 },
- { code: '370000', name: '山东省', parent_code: null, level: 1 },
- { code: '410000', name: '河南省', parent_code: null, level: 1 },
- { code: '420000', name: '湖北省', parent_code: null, level: 1 },
- { code: '430000', name: '湖南省', parent_code: null, level: 1 },
- { code: '440000', name: '广东省', parent_code: null, level: 1 },
- { code: '450000', name: '广西壮族自治区', parent_code: null, level: 1 },
- { code: '460000', name: '海南省', parent_code: null, level: 1 },
- { code: '500000', name: '重庆市', parent_code: null, level: 1 },
- { code: '510000', name: '四川省', parent_code: null, level: 1 },
- { code: '520000', name: '贵州省', parent_code: null, level: 1 },
- { code: '530000', name: '云南省', parent_code: null, level: 1 },
- { code: '540000', name: '西藏自治区', parent_code: null, level: 1 },
- { code: '610000', name: '陕西省', parent_code: null, level: 1 },
- { code: '620000', name: '甘肃省', parent_code: null, level: 1 },
- { code: '630000', name: '青海省', parent_code: null, level: 1 },
- { code: '640000', name: '宁夏回族自治区', parent_code: null, level: 1 },
- { code: '650000', name: '新疆维吾尔自治区', parent_code: null, level: 1 },
-
- // 浙江省市级
- { code: '330100', name: '杭州市', parent_code: '330000', level: 2 },
- { code: '330200', name: '宁波市', parent_code: '330000', level: 2 },
- { code: '330300', name: '温州市', parent_code: '330000', level: 2 },
- { code: '330400', name: '嘉兴市', parent_code: '330000', level: 2 },
- { code: '330500', name: '湖州市', parent_code: '330000', level: 2 },
- { code: '330600', name: '绍兴市', parent_code: '330000', level: 2 },
- { code: '330700', name: '金华市', parent_code: '330000', level: 2 },
- { code: '330800', name: '衢州市', parent_code: '330000', level: 2 },
- { code: '330900', name: '舟山市', parent_code: '330000', level: 2 },
- { code: '331000', name: '台州市', parent_code: '330000', level: 2 },
- { code: '331100', name: '丽水市', parent_code: '330000', level: 2 },
-
- // 杭州市区级
- { code: '330102', name: '上城区', parent_code: '330100', level: 3 },
- { code: '330105', name: '拱墅区', parent_code: '330100', level: 3 },
- { code: '330106', name: '西湖区', parent_code: '330100', level: 3 },
- { code: '330108', name: '滨江区', parent_code: '330100', level: 3 },
- { code: '330109', name: '萧山区', parent_code: '330100', level: 3 },
- { code: '330110', name: '余杭区', parent_code: '330100', level: 3 },
- { code: '330111', name: '富阳区', parent_code: '330100', level: 3 },
- { code: '330112', name: '临安区', parent_code: '330100', level: 3 },
- { code: '330113', name: '临平区', parent_code: '330100', level: 3 },
- { code: '330114', name: '钱塘区', parent_code: '330100', level: 3 },
- { code: '330122', name: '桐庐县', parent_code: '330100', level: 3 },
- { code: '330127', name: '淳安县', parent_code: '330100', level: 3 },
- { code: '330182', name: '建德市', parent_code: '330100', level: 3 }
- ];
-
- for (const region of regions) {
- try {
- await getDB().execute(`
- INSERT IGNORE INTO china_regions (code, name, parent_code, level)
- VALUES (?, ?, ?, ?)
- `, [region.code, region.name, region.parent_code, region.level]);
- } catch (error) {
- console.log(`区域${region.name}创建失败:`, error.message);
- }
- }
- console.log('全国省市区数据初始化完成');
-}
module.exports = {
- initDatabase,
- createTables,
- addMissingFields,
- createDefaultData,
- initializeZhejiangRegions,
- initializeDefaultAddressLabels,
- initializeChinaRegions
+ initDatabase
};
\ No newline at end of file
diff --git a/db-monitor.js b/db-monitor.js
deleted file mode 100644
index 0b08724..0000000
--- a/db-monitor.js
+++ /dev/null
@@ -1,295 +0,0 @@
-const { getDB, dbConfig } = require('./database');
-const { logger } = require('./config/logger');
-
-/**
- * 数据库连接监控工具
- * 用于诊断和监控数据库连接池状态
- */
-class DatabaseMonitor {
- /**
- * 获取连接池详细状态
- * @returns {Object} 连接池状态信息
- */
- /**
- * 获取连接池详细状态
- * @returns {Object} 连接池状态信息
- */
- getPoolStatus() {
- try {
- const pool = getDB();
-
- const status = {
- // 基本连接信息
- totalConnections: pool._allConnections ? pool._allConnections.length : 0,
- freeConnections: pool._freeConnections ? pool._freeConnections.length : 0,
- acquiringConnections: pool._acquiringConnections ? pool._acquiringConnections.length : 0,
-
- // 计算使用率
- connectionLimit: dbConfig.connectionLimit,
- usageRate: 0,
-
- // 配置信息
- config: {
- connectionLimit: dbConfig.connectionLimit,
- acquireTimeout: dbConfig.acquireTimeout,
- timeout: dbConfig.timeout,
- idleTimeout: dbConfig.idleTimeout,
- maxLifetime: dbConfig.maxLifetime,
- queueLimit: dbConfig.queueLimit,
- host: dbConfig.host,
- database: dbConfig.database
- },
-
- // 时间戳
- timestamp: new Date().toISOString()
- };
-
- // 计算连接使用率
- if (status.connectionLimit > 0) {
- const usedConnections = status.totalConnections - status.freeConnections;
- status.usageRate = Math.round((usedConnections / status.connectionLimit) * 100);
- }
-
- return status;
- } catch (error) {
- logger.error('Failed to get pool status', { error: error.message });
- return {
- error: error.message,
- timestamp: new Date().toISOString()
- };
- }
- }
-
- /**
- * 测试数据库连接
- * @returns {Object} 连接测试结果
- */
- async testConnection() {
- const startTime = Date.now();
- let connection;
-
- try {
- const pool = getDB();
-
- // 获取连接
- const acquireStart = Date.now();
- connection = await pool.getConnection();
- const acquireTime = Date.now() - acquireStart;
-
- // 执行测试查询
- const queryStart = Date.now();
- const [result] = await connection.execute('SELECT 1 as test, NOW() as server_time');
- const queryTime = Date.now() - queryStart;
-
- const totalTime = Date.now() - startTime;
-
- return {
- success: true,
- acquireTime,
- queryTime,
- totalTime,
- serverTime: result[0].server_time,
- connectionId: connection.threadId,
- timestamp: new Date().toISOString()
- };
-
- } catch (error) {
- const totalTime = Date.now() - startTime;
-
- logger.error('Database connection test failed', {
- error: error.message,
- totalTime
- });
-
- return {
- success: false,
- error: error.message,
- errorCode: error.code,
- totalTime,
- timestamp: new Date().toISOString()
- };
- } finally {
- if (connection) {
- connection.release();
- }
- }
- }
-
- /**
- * 执行连接池诊断
- * @returns {Object} 诊断结果
- */
- async diagnose() {
- const poolStatus = this.getPoolStatus();
- const connectionTest = await this.testConnection();
-
- const diagnosis = {
- poolStatus,
- connectionTest,
- issues: [],
- recommendations: [],
- timestamp: new Date().toISOString()
- };
-
- // 分析潜在问题
- if (poolStatus.usageRate > 90) {
- diagnosis.issues.push('连接池使用率过高 (>90%)');
- diagnosis.recommendations.push('考虑增加连接池大小或优化查询性能');
- }
-
- if (poolStatus.freeConnections === 0) {
- diagnosis.issues.push('没有空闲连接可用');
- diagnosis.recommendations.push('立即检查是否存在连接泄漏或增加连接池大小');
- }
-
- if (!connectionTest.success) {
- diagnosis.issues.push(`数据库连接失败: ${connectionTest.error}`);
- diagnosis.recommendations.push('检查数据库服务器状态和网络连接');
- } else {
- if (connectionTest.acquireTime > 5000) {
- diagnosis.issues.push('获取连接耗时过长 (>5秒)');
- diagnosis.recommendations.push('检查连接池配置和数据库负载');
- }
-
- if (connectionTest.queryTime > 1000) {
- diagnosis.issues.push('查询响应时间过长 (>1秒)');
- diagnosis.recommendations.push('检查数据库性能和网络延迟');
- }
- }
-
- return diagnosis;
- }
-
- /**
- * 生成监控报告
- * @returns {string} 格式化的监控报告
- */
- async generateReport() {
- const diagnosis = await this.diagnose();
-
- let report = '\n=== 数据库连接监控报告 ===\n';
- report += `生成时间: ${diagnosis.timestamp}\n\n`;
-
- // 连接池状态
- report += '【连接池状态】\n';
- if (diagnosis.poolStatus.error) {
- report += `错误: ${diagnosis.poolStatus.error}\n`;
- } else {
- report += `总连接数: ${diagnosis.poolStatus.totalConnections}\n`;
- report += `空闲连接: ${diagnosis.poolStatus.freeConnections}\n`;
- report += `获取中连接: ${diagnosis.poolStatus.acquiringConnections}\n`;
- report += `连接限制: ${diagnosis.poolStatus.connectionLimit}\n`;
- report += `使用率: ${diagnosis.poolStatus.usageRate}%\n`;
- }
-
- // 连接测试
- report += '\n【连接测试】\n';
- if (diagnosis.connectionTest.success) {
- report += `状态: 成功\n`;
- report += `获取连接耗时: ${diagnosis.connectionTest.acquireTime}ms\n`;
- report += `查询耗时: ${diagnosis.connectionTest.queryTime}ms\n`;
- report += `总耗时: ${diagnosis.connectionTest.totalTime}ms\n`;
- report += `连接ID: ${diagnosis.connectionTest.connectionId}\n`;
- report += `服务器时间: ${diagnosis.connectionTest.serverTime}\n`;
- } else {
- report += `状态: 失败\n`;
- report += `错误: ${diagnosis.connectionTest.error}\n`;
- report += `错误代码: ${diagnosis.connectionTest.errorCode || 'N/A'}\n`;
- report += `总耗时: ${diagnosis.connectionTest.totalTime}ms\n`;
- }
-
- // 问题和建议
- if (diagnosis.issues.length > 0) {
- report += '\n【发现的问题】\n';
- diagnosis.issues.forEach((issue, index) => {
- report += `${index + 1}. ${issue}\n`;
- });
- }
-
- if (diagnosis.recommendations.length > 0) {
- report += '\n【建议】\n';
- diagnosis.recommendations.forEach((rec, index) => {
- report += `${index + 1}. ${rec}\n`;
- });
- }
-
- if (diagnosis.issues.length === 0) {
- report += '\n【状态】\n数据库连接正常,未发现问题。\n';
- }
-
- report += '\n=== 报告结束 ===\n';
-
- return report;
- }
-
- /**
- * 启动实时监控
- * @param {number} interval 监控间隔(毫秒),默认30秒
- */
- startMonitoring(interval = 30000) {
- console.log('启动数据库连接实时监控...');
-
- const monitor = async () => {
- try {
- const status = this.getPoolStatus();
-
- // 只在有问题时输出详细信息
- if (status.usageRate > 80 || status.freeConnections < 2) {
- console.warn('数据库连接池警告:', {
- usageRate: `${status.usageRate}%`,
- freeConnections: status.freeConnections,
- totalConnections: status.totalConnections
- });
- }
-
- // 减少频繁的日志记录,只在有问题时记录
- if (status.usageRate > 80 || status.freeConnections < 2) {
- logger.warn('Database pool status warning', status);
- }
- // 注释掉正常情况下的日志记录
- // logger.info('Database pool status', status);
-
- } catch (error) {
- console.error('监控过程中发生错误:', error);
- logger.error('Database monitoring error', { error: error.message });
- }
- };
-
- // 立即执行一次
- monitor();
-
- // 定期执行
- const intervalId = setInterval(monitor, interval);
-
- // 返回停止函数
- return () => {
- clearInterval(intervalId);
- console.log('数据库连接监控已停止');
- };
- }
-}
-
-// 创建单例实例
-const dbMonitor = new DatabaseMonitor();
-
-// 如果直接运行此文件,执行诊断
-if (require.main === module) {
- (async () => {
- try {
- // 初始化数据库
- const { initDB } = require('./database');
- await initDB();
-
- console.log('正在执行数据库连接诊断...');
- const report = await dbMonitor.generateReport();
- console.log(report);
-
- process.exit(0);
- } catch (error) {
- console.error('诊断失败:', error);
- process.exit(1);
- }
- })();
-}
-
-module.exports = dbMonitor;
\ No newline at end of file
diff --git a/docs/apis/captcha.js b/docs/apis/captcha.js
new file mode 100644
index 0000000..3a46213
--- /dev/null
+++ b/docs/apis/captcha.js
@@ -0,0 +1,87 @@
+/**
+ * @swagger
+ * tags:
+ * name: Captcha
+ * description: 验证码API
+ */
+/**
+ * @swagger
+ * /captcha/generate:
+ * get:
+ * summary: 生成图形验证码
+ * tags: [Captcha]
+ * responses:
+ * 200:
+ * description: 成功生成验证码
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * example: true
+ * data:
+ * type: object
+ * properties:
+ * captchaId:
+ * type: string
+ * description: 验证码唯一ID
+ * image:
+ * type: string
+ * description: Base64编码的SVG验证码图片
+ * 500:
+ * description: 服务器错误
+ */
+/**
+ * @swagger
+ * /captcha/verify:
+ * post:
+ * summary: 验证用户输入的验证码
+ * tags: [Captcha]
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * required:
+ * - captchaId
+ * - captchaText
+ * properties:
+ * captchaId:
+ * type: string
+ * description: 验证码唯一ID
+ * captchaText:
+ * type: string
+ * description: 用户输入的验证码
+ * responses:
+ * 200:
+ * description: 验证码验证成功
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * example: true
+ * message:
+ * type: string
+ * example: 验证码验证成功
+ * 400:
+ * description: 验证码错误或已过期
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * example: false
+ * message:
+ * type: string
+ * example: 验证码错误
+ * 500:
+ * description: 服务器错误
+ */
\ No newline at end of file
diff --git a/docs/apis/matching.js b/docs/apis/matching.js
new file mode 100644
index 0000000..7e7a72b
--- /dev/null
+++ b/docs/apis/matching.js
@@ -0,0 +1,159 @@
+/**
+ * @swagger
+ * tags:
+ * name: Matching
+ * description: 匹配订单相关接口
+ */
+/**
+ * @swagger
+ * /api/matching/my-orders:
+ * get:
+ * summary: 获取用户的匹配订单列表
+ * tags: [Matching]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: query
+ * name: page
+ * schema:
+ * type: integer
+ * default: 1
+ * description: 页码
+ * - in: query
+ * name: limit
+ * schema:
+ * type: integer
+ * default: 10
+ * description: 每页数量
+ * responses:
+ * 200:
+ * description: 成功获取匹配订单列表
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * data:
+ * type: array
+ * items:
+ * $ref: '#/components/schemas/MatchingOrder'
+ * 401:
+ * description: 未授权
+ * 500:
+ * description: 服务器错误
+ */
+/**
+ * @swagger
+ * components:
+ * schemas:
+ * MatchingOrder:
+ * type: object
+ * properties:
+ * id:
+ * type: integer
+ * description: 匹配订单ID
+ * initiator_id:
+ * type: integer
+ * description: 发起人ID
+ * matching_type:
+ * type: string
+ * enum: [small, large]
+ * description: 匹配类型(小额或大额)
+ * amount:
+ * type: number
+ * description: 匹配总金额
+ * status:
+ * type: string
+ * enum: [pending, matching, completed, failed]
+ * description: 订单状态
+ * created_at:
+ * type: string
+ * format: date-time
+ * description: 创建时间
+ * Allocation:
+ * type: object
+ * properties:
+ * id:
+ * type: integer
+ * description: 分配ID
+ * from_user_id:
+ * type: integer
+ * description: 发送方用户ID
+ * to_user_id:
+ * type: integer
+ * description: 接收方用户ID
+ * amount:
+ * type: number
+ * description: 分配金额
+ * cycle_number:
+ * type: integer
+ * description: 轮次编号
+ * status:
+ * type: string
+ * enum: [pending, confirmed, rejected, cancelled]
+ * description: 分配状态
+ * created_at:
+ * type: string
+ * format: date-time
+ * description: 创建时间
+ */
+
+/**
+ * @swagger
+ * /api/matching/create:
+ * post:
+ * summary: 创建匹配订单
+ * tags: [Matching]
+ * security:
+ * - bearerAuth: []
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * matchingType:
+ * type: string
+ * enum: [small, large]
+ * default: small
+ * description: 匹配类型(小额或大额)
+ * customAmount:
+ * type: number
+ * description: 大额匹配时的自定义金额(5000-50000之间)
+ * responses:
+ * 200:
+ * description: 匹配订单创建成功
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * message:
+ * type: string
+ * data:
+ * type: object
+ * properties:
+ * matchingOrderId:
+ * type: integer
+ * amounts:
+ * type: array
+ * items:
+ * type: number
+ * matchingType:
+ * type: string
+ * totalAmount:
+ * type: number
+ * 400:
+ * description: 参数错误或用户未满足匹配条件
+ * 401:
+ * description: 未授权
+ * 404:
+ * description: 用户不存在
+ * 500:
+ * description: 服务器错误
+ */
\ No newline at end of file
diff --git a/docs/apis/transfers.js b/docs/apis/transfers.js
new file mode 100644
index 0000000..1db9f85
--- /dev/null
+++ b/docs/apis/transfers.js
@@ -0,0 +1,388 @@
+/**
+ * @swagger
+ * components:
+ * schemas:
+ * Transfer:
+ * type: object
+ * properties:
+ * id:
+ * type: integer
+ * description: 转账记录ID
+ * user_id:
+ * type: integer
+ * description: 用户ID
+ * recipient_id:
+ * type: integer
+ * description: 接收方用户ID
+ * amount:
+ * type: number
+ * format: float
+ * description: 转账金额
+ * status:
+ * type: string
+ * enum: [pending, completed, failed, cancelled]
+ * description: 转账状态
+ * transfer_type:
+ * type: string
+ * enum: [user_to_user, user_to_system, system_to_user]
+ * description: 转账类型
+ * voucher_image:
+ * type: string
+ * description: 转账凭证图片路径
+ * remark:
+ * type: string
+ * description: 转账备注
+ * created_at:
+ * type: string
+ * format: date-time
+ * description: 创建时间
+ * updated_at:
+ * type: string
+ * format: date-time
+ * description: 更新时间
+ * Pagination:
+ * type: object
+ * properties:
+ * total:
+ * type: integer
+ * description: 总记录数
+ * page:
+ * type: integer
+ * description: 当前页码
+ * limit:
+ * type: integer
+ * description: 每页记录数
+ * total_pages:
+ * type: integer
+ * description: 总页数
+ */
+/**
+ * @swagger
+ * /transfers:
+ * get:
+ * summary: 获取转账列表
+ * tags: [Transfers]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: query
+ * name: status
+ * schema:
+ * type: string
+ * description: 转账状态过滤
+ * - in: query
+ * name: transfer_type
+ * schema:
+ * type: string
+ * description: 转账类型过滤
+ * - in: query
+ * name: start_date
+ * schema:
+ * type: string
+ * format: date
+ * description: 开始日期过滤
+ * - in: query
+ * name: end_date
+ * schema:
+ * type: string
+ * format: date
+ * description: 结束日期过滤
+ * - in: query
+ * name: search
+ * schema:
+ * type: string
+ * description: 搜索关键词(用户名或真实姓名)
+ * - in: query
+ * name: page
+ * schema:
+ * type: integer
+ * default: 1
+ * description: 页码
+ * - in: query
+ * name: limit
+ * schema:
+ * type: integer
+ * default: 10
+ * description: 每页数量
+ * - in: query
+ * name: sort
+ * schema:
+ * type: string
+ * description: 排序字段
+ * - in: query
+ * name: order
+ * schema:
+ * type: string
+ * enum: [asc, desc]
+ * description: 排序方向
+ * responses:
+ * 200:
+ * description: 成功获取转账列表
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * data:
+ * type: object
+ * properties:
+ * transfers:
+ * type: array
+ * items:
+ * $ref: '#/components/schemas/Transfer'
+ * pagination:
+ * $ref: '#/components/schemas/Pagination'
+ * 401:
+ * description: 未授权
+ * 500:
+ * description: 服务器错误
+ */
+/**
+ * @swagger
+ * /transfers/list:
+ * get:
+ * summary: 获取转账记录列表
+ * tags: [Transfers]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: query
+ * name: status
+ * schema:
+ * type: string
+ * description: 转账状态过滤
+ * - in: query
+ * name: transfer_type
+ * schema:
+ * type: string
+ * description: 转账类型过滤
+ * - in: query
+ * name: start_date
+ * schema:
+ * type: string
+ * format: date
+ * description: 开始日期过滤
+ * - in: query
+ * name: end_date
+ * schema:
+ * type: string
+ * format: date
+ * description: 结束日期过滤
+ * - in: query
+ * name: page
+ * schema:
+ * type: integer
+ * default: 1
+ * description: 页码
+ * - in: query
+ * name: limit
+ * schema:
+ * type: integer
+ * default: 10
+ * description: 每页数量
+ * - in: query
+ * name: sort
+ * schema:
+ * type: string
+ * description: 排序字段
+ * - in: query
+ * name: order
+ * schema:
+ * type: string
+ * enum: [asc, desc]
+ * description: 排序方向
+ * responses:
+ * 200:
+ * description: 成功获取转账记录列表
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * data:
+ * type: object
+ * properties:
+ * transfers:
+ * type: array
+ * items:
+ * $ref: '#/components/schemas/Transfer'
+ * pagination:
+ * $ref: '#/components/schemas/Pagination'
+ * 401:
+ * description: 未授权
+ * 500:
+ * description: 服务器错误
+ */
+/**
+ * @swagger
+ * /transfers/public-account:
+ * get:
+ * summary: 获取公户信息
+ * tags: [Transfers]
+ * security:
+ * - bearerAuth: []
+ * responses:
+ * 200:
+ * description: 成功获取公户信息
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * example: true
+ * data:
+ * type: object
+ * properties:
+ * id:
+ * type: integer
+ * description: 公户ID
+ * username:
+ * type: string
+ * description: 公户用户名
+ * example: public_account
+ * real_name:
+ * type: string
+ * description: 公户名称
+ * balance:
+ * type: number
+ * format: float
+ * description: 公户余额
+ * 401:
+ * description: 未授权
+ * 404:
+ * description: 公户不存在
+ * 500:
+ * description: 服务器错误
+ */
+/**
+ * @swagger
+ * /transfers/create:
+ * post:
+ * summary: 创建转账记录
+ * tags: [Transfers]
+ * security:
+ * - bearerAuth: []
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * required:
+ * - to_user_id
+ * - amount
+ * - transfer_type
+ * properties:
+ * to_user_id:
+ * type: integer
+ * description: 接收方用户ID
+ * amount:
+ * type: number
+ * format: float
+ * description: 转账金额
+ * transfer_type:
+ * type: string
+ * enum: [user_to_user, user_to_system, system_to_user]
+ * description: 转账类型
+ * remark:
+ * type: string
+ * description: 转账备注
+ * responses:
+ * 201:
+ * description: 转账记录创建成功
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * example: true
+ * message:
+ * type: string
+ * example: 转账记录创建成功,等待确认
+ * data:
+ * type: object
+ * properties:
+ * transfer_id:
+ * type: integer
+ * description: 转账记录ID
+ * 400:
+ * description: 请求参数错误
+ * 401:
+ * description: 未授权
+ * 500:
+ * description: 服务器错误
+ */
+/**
+ * @swagger
+ * /transfers/admin/create:
+ * post:
+ * summary: 管理员创建转账记录
+ * tags: [Transfers]
+ * security:
+ * - bearerAuth: []
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * required:
+ * - from_user_id
+ * - to_user_id
+ * - amount
+ * - transfer_type
+ * properties:
+ * from_user_id:
+ * type: integer
+ * description: 发送方用户ID
+ * to_user_id:
+ * type: integer
+ * description: 接收方用户ID
+ * amount:
+ * type: number
+ * format: float
+ * description: 转账金额
+ * transfer_type:
+ * type: string
+ * enum: [user_to_user, user_to_system, system_to_user]
+ * description: 转账类型
+ * description:
+ * type: string
+ * description: 转账描述
+ * responses:
+ * 201:
+ * description: 转账记录创建成功
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * example: true
+ * message:
+ * type: string
+ * example: 转账记录创建成功
+ * data:
+ * type: object
+ * properties:
+ * transfer_id:
+ * type: integer
+ * description: 转账记录ID
+ * 400:
+ * description: 请求参数错误
+ * 401:
+ * description: 未授权
+ * 403:
+ * description: 权限不足
+ * 500:
+ * description: 服务器错误
+ */
\ No newline at end of file
diff --git a/docs/apis/user.js b/docs/apis/user.js
new file mode 100644
index 0000000..6055bc1
--- /dev/null
+++ b/docs/apis/user.js
@@ -0,0 +1,367 @@
+/**
+ * @swagger
+ * tags:
+ * name: Authentication
+ * description: 用户认证API
+ */
+/**
+ * @swagger
+ * components:
+ * schemas:
+ * LoginCredentials:
+ * type: object
+ * required:
+ * - username
+ * - password
+ * properties:
+ * username:
+ * type: string
+ * description: 用户名或手机号
+ * password:
+ * type: string
+ * description: 密码
+ * RegisterRequest:
+ * type: object
+ * required:
+ * - username
+ * - phone
+ * - password
+ * - registrationCode
+ * - city
+ * - district_id
+ * - captchaId
+ * - captchaText
+ * - smsCode
+ * properties:
+ * username:
+ * type: string
+ * description: 用户名
+ * phone:
+ * type: string
+ * description: 手机号
+ * password:
+ * type: string
+ * description: 密码
+ * registrationCode:
+ * type: string
+ * description: 注册激活码
+ * city:
+ * type: string
+ * description: 城市
+ * district_id:
+ * type: string
+ * description: 区域ID
+ * captchaId:
+ * type: string
+ * description: 图形验证码ID
+ * captchaText:
+ * type: string
+ * description: 图形验证码文本
+ * smsCode:
+ * type: string
+ * description: 短信验证码
+ * role:
+ * type: string
+ * description: 用户角色
+ * default: user
+ */
+/**
+ * @swagger
+ * /auth/register:
+ * post:
+ * summary: 用户注册
+ * description: 需要提供有效的激活码才能注册
+ * tags: [Authentication]
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/RegisterRequest'
+ * responses:
+ * 201:
+ * description: 用户注册成功
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * message:
+ * type: string
+ * token:
+ * type: string
+ * description: JWT认证令牌
+ * user:
+ * type: object
+ * properties:
+ * id:
+ * type: integer
+ * username:
+ * type: string
+ * role:
+ * type: string
+ * 400:
+ * description: 请求参数错误
+ * 500:
+ * description: 服务器错误
+ */
+
+/**
+ * @swagger
+ * /auth/login:
+ * post:
+ * summary: 用户登录
+ * tags: [Authentication]
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/LoginCredentials'
+ * responses:
+ * 200:
+ * description: 登录成功
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * message:
+ * type: string
+ * token:
+ * type: string
+ * description: JWT认证令牌
+ * user:
+ * type: object
+ * properties:
+ * id:
+ * type: integer
+ * username:
+ * type: string
+ * role:
+ * type: string
+ * avatar:
+ * type: string
+ * points:
+ * type: integer
+ * 400:
+ * description: 请求参数错误
+ * 401:
+ * description: 用户名或密码错误
+ * 403:
+ * description: 账户审核未通过
+ * 500:
+ * description: 服务器错误
+ */
+/**
+ * @swagger
+ * /api/users/{id}/distribute:
+ * put:
+ * summary: 设置用户分发状态
+ * description: 更新指定用户的分发状态
+ * tags: [Users]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: path
+ * name: id
+ * required: true
+ * schema:
+ * type: integer
+ * description: 用户ID
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * required:
+ * - is_distribute
+ * properties:
+ * is_distribute:
+ * type: boolean
+ * description: 分发状态,true为启用分发,false为禁用分发
+ * example: true
+ * responses:
+ * 200:
+ * description: 分发状态更新成功
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * example: true
+ * message:
+ * type: string
+ * example: "分发状态更新成功"
+ * is_distribute:
+ * type: boolean
+ * description: 更新后的分发状态
+ * example: true
+ * 400:
+ * description: 请求参数错误
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * example: false
+ * message:
+ * type: string
+ * example: "分发状态无效"
+ * 404:
+ * description: 用户不存在
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * example: false
+ * message:
+ * type: string
+ * example: "用户不存在"
+ * 500:
+ * description: 服务器内部错误
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * example: false
+ * message:
+ * type: string
+ * example: "服务器内部错误"
+ */
+/**
+ * @swagger
+ * components:
+ * schemas:
+ * User:
+ * type: object
+ * required:
+ * - username
+ * - password
+ * - real_name
+ * - id_card
+ * properties:
+ * id:
+ * type: integer
+ * description: 用户ID
+ * username:
+ * type: string
+ * description: 用户名
+ * role:
+ * type: string
+ * description: 用户角色
+ * enum: [user, admin, merchant]
+ * avatar:
+ * type: string
+ * description: 用户头像URL
+ * points:
+ * type: integer
+ * description: 用户积分
+ * real_name:
+ * type: string
+ * description: 真实姓名
+ * id_card:
+ * type: string
+ * description: 身份证号
+ * phone:
+ * type: string
+ * description: 手机号
+ * is_system_account:
+ * type: boolean
+ * description: 是否为系统账户
+ * is_distribute:
+ * type: boolean
+ * description: 是否为分发账户
+ * created_at:
+ * type: string
+ * format: date-time
+ * description: 创建时间
+ * updated_at:
+ * type: string
+ * format: date-time
+ * description: 更新时间
+ */
+
+/**
+ * @swagger
+ * /users:
+ * post:
+ * summary: 创建用户(管理员权限)
+ * tags: [Users]
+ * security:
+ * - bearerAuth: []
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * required:
+ * - username
+ * - password
+ * - real_name
+ * - id_card
+ * properties:
+ * username:
+ * type: string
+ * password:
+ * type: string
+ * role:
+ * type: string
+ * enum: [user, admin, merchant]
+ * default: user
+ * is_system_account:
+ * type: boolean
+ * default: false
+ * real_name:
+ * type: string
+ * id_card:
+ * type: string
+ * wechat_qr:
+ * type: string
+ * alipay_qr:
+ * type: string
+ * bank_card:
+ * type: string
+ * unionpay_qr:
+ * type: string
+ * phone:
+ * type: string
+ * responses:
+ * 201:
+ * description: 用户创建成功
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * message:
+ * type: string
+ * user:
+ * $ref: '#/components/schemas/User'
+ * 400:
+ * description: 请求参数错误
+ * 401:
+ * description: 未授权
+ * 403:
+ * description: 权限不足
+ * 500:
+ * description: 服务器错误
+ */
\ No newline at end of file
diff --git a/routes/agents.js b/routes/agents.js
index f40b9ff..7af70a3 100644
--- a/routes/agents.js
+++ b/routes/agents.js
@@ -4,6 +4,8 @@ const { getDB } = require('../database');
const QRCode = require('qrcode');
const crypto = require('crypto');
const bcrypt = require('bcryptjs');
+const { auth } = require('../middleware/auth');
+const dayjs = require('dayjs');
// 获取浙江省所有区域列表
router.get('/regions', async (req, res) => {
@@ -22,7 +24,7 @@ router.get('/regions', async (req, res) => {
router.post('/apply', async (req, res) => {
try {
const { region_id, real_name, phone, id_card, contact_address } = req.body;
-
+
if (!region_id || !real_name || !phone || !id_card) {
return res.status(400).json({ success: false, message: '请填写完整信息' });
}
@@ -51,13 +53,13 @@ router.post('/apply', async (req, res) => {
let userId;
if (existingUser.length > 0) {
userId = existingUser[0].id;
-
+
// 检查该用户是否已申请过代理(包括所有状态)
const [existingUserAgent] = await getDB().execute(
'SELECT id, status, region_id FROM regional_agents WHERE user_id = ?',
[userId]
);
-
+
if (existingUserAgent.length > 0) {
const agentStatus = existingUserAgent[0].status;
if (agentStatus === 'active') {
@@ -73,7 +75,7 @@ router.post('/apply', async (req, res) => {
const bcrypt = require('bcryptjs');
const tempPassword = Math.random().toString(36).slice(-8); // 生成8位临时密码
const hashedPassword = await bcrypt.hash(tempPassword, 10);
-
+
const [userResult] = await getDB().execute(
'INSERT INTO users (username, password, phone, real_name, id_card, created_at) VALUES (?, ?, ?, ?, ?, NOW())',
[phone, hashedPassword, phone, real_name, id_card]
@@ -101,7 +103,7 @@ router.post('/apply', async (req, res) => {
router.post('/login', async (req, res) => {
try {
const { phone, password } = req.body;
-
+
if (!phone || !password) {
return res.status(400).json({ success: false, message: '请输入手机号和密码' });
}
@@ -121,7 +123,7 @@ router.post('/login', async (req, res) => {
}
const agent = agents[0];
-
+
// 验证密码
const isPasswordValid = await bcrypt.compare(password, agent.password);
if (!isPasswordValid) {
@@ -132,9 +134,9 @@ router.post('/login', async (req, res) => {
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
const token = jwt.sign(
- {
- userId: agent.user_id,
- username: agent.username || agent.phone,
+ {
+ userId: agent.user_id,
+ username: agent.username || agent.phone,
role: agent.role || 'agent',
agentId: agent.id
},
@@ -144,13 +146,13 @@ router.post('/login', async (req, res) => {
delete agent.password; // 不返回密码
- res.json({
- success: true,
+ res.json({
+ success: true,
data: {
...agent,
token
- },
- message: '登录成功'
+ },
+ message: '登录成功'
});
} catch (error) {
console.error('代理登录失败:', error);
@@ -158,56 +160,6 @@ router.post('/login', async (req, res) => {
}
});
-// 生成注册二维码
-router.post('/generate-invite-code', async (req, res) => {
- try {
- const { agent_id } = req.body;
-
- if (!agent_id) {
- return res.status(400).json({ success: false, message: '代理ID不能为空' });
- }
-
- // 验证代理是否存在且激活,并获取对应的user_id
- const [agents] = await getDB().execute(
- 'SELECT id, user_id FROM regional_agents WHERE id = ? AND status = "active"',
- [parseInt(agent_id)]
- );
-
- if (agents.length === 0) {
- return res.status(404).json({ success: false, message: '代理不存在或未激活' });
- }
-
- const userIdForAgent = agents[0].user_id;
-
- // 生成唯一激活码
- const code = crypto.randomBytes(8).toString('hex').toUpperCase();
- const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30天后过期
-
- // 插入激活码记录(created_by_admin_id设为3608,agent_id使用user_id)
- await getDB().execute(
- `INSERT INTO registration_codes (code, expires_at, created_by_admin_id, agent_id, is_used, created_at) VALUES ('${code}', '${expiresAt.toISOString().slice(0, 19).replace('T', ' ')}', 3608, ${userIdForAgent}, 0, NOW())`
- );
-
- // 生成二维码 - 使用注册页面URL(不包含邀请码参数)
- const registerUrl = `${process.env.FRONTEND_URL || 'https://www.zrbjr.com/frontend'}/register`;
-
- const qrCodeUrl = await QRCode.toDataURL(registerUrl);
-
- res.json({
- success: true,
- data: {
- code: code,
- qr_code: qrCodeUrl,
- expires_at: expiresAt
- },
- message: '二维码生成成功'
- });
- } catch (error) {
- console.error('生成二维码失败:', error);
- res.status(500).json({ success: false, message: '生成二维码失败' });
- }
-});
-
// 获取代理的商户列表(包含所有商户,标注早期商户状态)
router.get('/merchants/:agent_id', async (req, res) => {
try {
@@ -221,11 +173,11 @@ router.get('/merchants/:agent_id', async (req, res) => {
[parseInt(agent_id)]
);
const regionId = agentInfo[0].region_id;
-
+
if (!agentInfo || agentInfo.length === 0) {
return res.status(404).json({ success: false, message: '代理不存在' });
}
-
+
const agentCreatedAt = agentInfo[0].agent_created_at;
// 获取商户列表(包含所有商户,包括agent_merchants表中的和符合条件的早期商户)
@@ -264,7 +216,7 @@ router.get('/merchants/:agent_id', async (req, res) => {
WHERE (am.agent_id = ? OR (u.created_at < ? AND u.district_id = ? AND u.role = 'user'))`,
[parseInt(agent_id), parseInt(agent_id), agentCreatedAt, parseInt(regionId)]
);
-
+
// 获取早期商户统计(从user表获取所有符合条件的早期商户)
// 早期商户的判断条件:1.早期商户注册时间比代理要早。2.代理商代理的区县与商户的区县一致
const [earlyMerchantStats] = await getDB().execute(
@@ -276,7 +228,7 @@ router.get('/merchants/:agent_id', async (req, res) => {
AND u.role = 'user'`,
[agentCreatedAt, parseInt(regionId)]
);
-
+
// 获取正常商户统计(包括代理关联的商户,排除符合条件的早期商户)
const [normalMerchantStats] = await getDB().execute(
`SELECT
@@ -287,8 +239,8 @@ router.get('/merchants/:agent_id', async (req, res) => {
[parseInt(agent_id), parseInt(agent_id), agentCreatedAt, parseInt(regionId)]
);
- res.json({
- success: true,
+ res.json({
+ success: true,
data: {
merchants,
total: parseInt(countResult[0].total),
@@ -336,8 +288,8 @@ router.get('/commissions/:agent_id', async (req, res) => {
summary[0].paid_commission = summary[0].total_commission;
summary[0].pending_commission = 0;
- res.json({
- success: true,
+ res.json({
+ success: true,
data: {
commissions,
summary: summary[0],
@@ -379,20 +331,20 @@ router.get('/list', async (req, res) => {
try {
const { page = 1, limit = 10, status, region_id } = req.query;
const offset = (page - 1) * limit;
-
+
let whereClause = '1=1';
let params = [];
-
+
if (status) {
whereClause += ' AND ra.status = ?';
params.push(status);
}
-
+
if (region_id) {
whereClause += ' AND ra.region_id = ?';
params.push(region_id);
}
-
+
// 获取代理列表
const [agents] = await getDB().execute(
`SELECT ra.*, u.username, u.phone, u.real_name, u.created_at as user_created_at,
@@ -404,7 +356,7 @@ router.get('/list', async (req, res) => {
ORDER BY ra.created_at DESC
LIMIT ${limit} OFFSET ${offset}`
);
-
+
// 获取总数
const [countResult] = await getDB().execute(
`SELECT COUNT(*) as total
@@ -413,10 +365,10 @@ router.get('/list', async (req, res) => {
JOIN zhejiang_regions zr ON ra.region_id = zr.id
WHERE ${whereClause}`
);
-
+
const total = countResult[0].total;
const totalPages = Math.ceil(total / limit);
-
+
res.json({
success: true,
data: {
@@ -446,7 +398,7 @@ router.get('/commission-trend/:agent_id', async (req, res) => {
try {
const { agent_id } = req.params;
const { period = '7d' } = req.query;
-
+
// 根据周期确定天数
let days;
switch (period) {
@@ -462,7 +414,7 @@ router.get('/commission-trend/:agent_id', async (req, res) => {
default:
days = 7;
}
-
+
// 获取指定时间范围内的佣金趋势数据
const [trendData] = await getDB().execute(
`SELECT
@@ -475,33 +427,33 @@ router.get('/commission-trend/:agent_id', async (req, res) => {
ORDER BY date ASC`,
[parseInt(agent_id), days]
);
-
+
// 填充缺失的日期(确保每天都有数据点)
const filledData = [];
const today = new Date();
-
+
for (let i = days - 1; i >= 0; i--) {
const date = new Date(today);
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split('T')[0];
-
+
// 修复日期比较:将数据库返回的Date对象转换为字符串进行比较
const existingData = trendData.find(item => {
- const itemDateStr = item.date instanceof Date ?
- item.date.toISOString().split('T')[0] :
+ const itemDateStr = item.date instanceof Date ?
+ item.date.toISOString().split('T')[0] :
item.date;
return itemDateStr === dateStr;
});
-
+
filledData.push({
date: dateStr,
amount: existingData ? parseFloat(existingData.amount) : 0
});
}
-
- res.json({
- success: true,
- data: filledData
+
+ res.json({
+ success: true,
+ data: filledData
});
} catch (error) {
console.error('获取佣金趋势数据失败:', error);
@@ -518,7 +470,7 @@ router.get('/commission-trend/:agent_id', async (req, res) => {
router.get('/merchant-status/:agent_id', async (req, res) => {
try {
const { agent_id } = req.params;
-
+
// 获取商户状态分布
const [statusData] = await getDB().execute(
`SELECT
@@ -536,10 +488,10 @@ router.get('/merchant-status/:agent_id', async (req, res) => {
ORDER BY count DESC`,
[parseInt(agent_id)]
);
-
- res.json({
- success: true,
- data: statusData
+
+ res.json({
+ success: true,
+ data: statusData
});
} catch (error) {
console.error('获取商户状态分布数据失败:', error);
@@ -556,7 +508,7 @@ router.get('/merchant-status/:agent_id', async (req, res) => {
router.get('/detailed-stats/:agent_id', async (req, res) => {
try {
const { agent_id } = req.params;
-
+
// 获取基础统计数据
const [basicStats] = await getDB().execute(
`SELECT
@@ -568,7 +520,7 @@ router.get('/detailed-stats/:agent_id', async (req, res) => {
(SELECT COUNT(*) FROM agent_commission_records WHERE agent_id = ?) as total_commission_records`,
[parseInt(agent_id), parseInt(agent_id), parseInt(agent_id), parseInt(agent_id), parseInt(agent_id), parseInt(agent_id)]
);
-
+
// 获取本月佣金
const [monthlyStats] = await getDB().execute(
`SELECT
@@ -580,7 +532,7 @@ router.get('/detailed-stats/:agent_id', async (req, res) => {
AND MONTH(created_at) = MONTH(CURDATE())`,
[parseInt(agent_id)]
);
-
+
// 获取今日佣金
const [dailyStats] = await getDB().execute(
`SELECT
@@ -591,7 +543,7 @@ router.get('/detailed-stats/:agent_id', async (req, res) => {
AND DATE(created_at) = CURDATE()`,
[parseInt(agent_id)]
);
-
+
// 获取最近7天新增商户数
const [weeklyMerchants] = await getDB().execute(
`SELECT COUNT(*) as weekly_new_merchants
@@ -601,7 +553,7 @@ router.get('/detailed-stats/:agent_id', async (req, res) => {
AND am.created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)`,
[parseInt(agent_id)]
);
-
+
// 获取提现相关统计数据
const [withdrawalStats] = await getDB().execute(
`SELECT
@@ -619,7 +571,7 @@ router.get('/detailed-stats/:agent_id', async (req, res) => {
WHERE ra.id = ?`,
[parseInt(agent_id), parseInt(agent_id)]
);
-
+
// 合并所有统计数据
const stats = {
...basicStats[0],
@@ -632,10 +584,10 @@ router.get('/detailed-stats/:agent_id', async (req, res) => {
available_amount: 0
})
};
-
- res.json({
- success: true,
- data: stats
+
+ res.json({
+ success: true,
+ data: stats
});
} catch (error) {
console.error('获取详细统计数据失败:', error);
@@ -658,17 +610,17 @@ router.get('/merchants/:agent_id/transfers', async (req, res) => {
const pageNum = parseInt(page) || 1;
const limitNum = parseInt(limit) || 10;
const offset = (pageNum - 1) * limitNum;
-
+
// 检查代理是否存在
const [agentResult] = await getDB().execute(
'SELECT * FROM regional_agents WHERE id = ?',
[parseInt(agent_id)]
);
-
+
if (agentResult.length === 0) {
return res.status(404).json({ success: false, message: '代理不存在' });
}
-
+
// 查询商户转账记录
const transferQuery = `
SELECT
@@ -694,7 +646,7 @@ router.get('/merchants/:agent_id/transfers', async (req, res) => {
LIMIT ${limitNum} OFFSET ${offset}
`;
const [transfers] = await getDB().execute(transferQuery, [parseInt(agent_id)]);
-
+
// 查询总数
const [totalResult] = await getDB().execute(
`SELECT COUNT(*) as total
@@ -703,9 +655,9 @@ router.get('/merchants/:agent_id/transfers', async (req, res) => {
WHERE am.agent_id = ?`,
[parseInt(agent_id)]
);
-
+
const total = totalResult[0].total;
-
+
res.json({
success: true,
data: {
@@ -723,5 +675,45 @@ router.get('/merchants/:agent_id/transfers', async (req, res) => {
res.status(500).json({ success: false, message: '获取代理商户转账记录失败,请稍后再试' });
}
});
+/**
+ * 获取分销列表
+ * @route GET /agents/distribution
+ * @returns {Object} 分销列表
+ */
+router.get('/distribution', auth, async (req, res) => {
+ try {
+ const { id } = req.user;
+ const { page = 1, size = 10 } = req.query;
+ const pageNum = parseInt(page) || 1;
+ const limitNum = parseInt(size) || 10;
+ const offset = (page - 1) * size;
+ const [result] = await getDB().execute(
+ `SELECT real_name,phone,username,avatar,created_at FROM users WHERE inviter = ? ORDER BY created_at DESC
+ LIMIT ${size} OFFSET ${offset}`,
+ [parseInt(id)]
+ );
+ const [totalResult] = await getDB().execute(
+ `SELECT COUNT(*) as total FROM users WHERE inviter = ? `,
+ [parseInt(id)]
+ );
+ result.forEach(item => {
+ item.created_at = dayjs(item.created_at).format('YYYY-MM-DD HH:mm:ss');
+ })
+
+ const total = totalResult[0].total;
+ res.json({
+ success: true, data: result, pagination: {
+ page: pageNum,
+ limit: limitNum,
+ total,
+ pages: Math.ceil(total / limitNum)
+ }
+ });
+ } catch (error) {
+ console.error('获取分销列表失败:', error);
+ res.status(500).json({ success: false, message: '获取分销列表失败' });
+ }
+});
+
module.exports = router;
\ No newline at end of file
diff --git a/routes/agents/agents.js b/routes/agents/agents.js
index d05b488..15420ff 100644
--- a/routes/agents/agents.js
+++ b/routes/agents/agents.js
@@ -3,6 +3,7 @@ const router = express.Router();
const { getDB } = require('../../database');
const bcrypt = require('bcryptjs');
const { auth, adminAuth } = require('../../middleware/auth');
+const dayjs = require('dayjs');
// 创建管理员认证中间件组合
const authenticateAdmin = [auth, adminAuth];
diff --git a/routes/auth.js b/routes/auth.js
index 654fe8f..1ba620c 100644
--- a/routes/auth.js
+++ b/routes/auth.js
@@ -5,119 +5,6 @@ const { getDB } = require('../database');
const router = express.Router();
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
-
-
-/**
- * @swagger
- * tags:
- * name: Authentication
- * description: 用户认证API
- */
-
-/**
- * @swagger
- * components:
- * schemas:
- * LoginCredentials:
- * type: object
- * required:
- * - username
- * - password
- * properties:
- * username:
- * type: string
- * description: 用户名或手机号
- * password:
- * type: string
- * description: 密码
- * RegisterRequest:
- * type: object
- * required:
- * - username
- * - phone
- * - password
- * - registrationCode
- * - city
- * - district_id
- * - captchaId
- * - captchaText
- * - smsCode
- * properties:
- * username:
- * type: string
- * description: 用户名
- * phone:
- * type: string
- * description: 手机号
- * password:
- * type: string
- * description: 密码
- * registrationCode:
- * type: string
- * description: 注册激活码
- * city:
- * type: string
- * description: 城市
- * district_id:
- * type: string
- * description: 区域ID
- * captchaId:
- * type: string
- * description: 图形验证码ID
- * captchaText:
- * type: string
- * description: 图形验证码文本
- * smsCode:
- * type: string
- * description: 短信验证码
- * role:
- * type: string
- * description: 用户角色
- * default: user
- */
-
-/**
- * @swagger
- * /auth/register:
- * post:
- * summary: 用户注册
- * description: 需要提供有效的激活码才能注册
- * tags: [Authentication]
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * $ref: '#/components/schemas/RegisterRequest'
- * responses:
- * 201:
- * description: 用户注册成功
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * message:
- * type: string
- * token:
- * type: string
- * description: JWT认证令牌
- * user:
- * type: object
- * properties:
- * id:
- * type: integer
- * username:
- * type: string
- * role:
- * type: string
- * 400:
- * description: 请求参数错误
- * 500:
- * description: 服务器错误
- */
router.post('/register', async (req, res) => {
try {
const db = getDB();
@@ -129,13 +16,15 @@ router.post('/register', async (req, res) => {
password,
city,
district_id: district,
+ province,
+ inviter = '',
captchaId,
captchaText,
smsCode, // 短信验证码
role = 'user'
} = req.body;
- if (!username || !phone || !password || !city || !district) {
+ if (!username || !phone || !password || !city || !district || !province) {
return res.status(400).json({ success: false, message: '用户名、手机号、密码、城市和区域不能为空' });
}
@@ -146,9 +35,6 @@ router.post('/register', async (req, res) => {
if (!smsCode) {
return res.status(400).json({ success: false, message: '短信验证码不能为空' });
}
-
- // 注意:图形验证码已在前端通过 /captcha/verify 接口验证过,这里不再重复验证
-
// 验证短信验证码
const smsAPI = require('./sms');
const smsValid = smsAPI.verifySMSCode(phone, smsCode);
@@ -171,14 +57,7 @@ router.post('/register', async (req, res) => {
);
if (existingUsers.length > 0) {
- const existingUser = existingUsers[0];
- // 如果用户存在但未支付,允许重新注册(覆盖原用户信息)
- if (existingUser.payment_status === 'unpaid') {
- // 删除未支付的用户记录
- await db.execute('DELETE FROM users WHERE id = ?', [existingUser.id]);
- } else {
- return res.status(400).json({ success: false, message: '用户名或手机号已存在' });
- }
+ return res.status(400).json({ success: false, message: '用户名或手机号已存在' });
}
// 加密密码
@@ -186,16 +65,11 @@ router.post('/register', async (req, res) => {
// 创建用户(初始状态为未支付)
const [result] = await db.execute(
- 'INSERT INTO users (username, phone, password, role, points, audit_status, city, district_id, payment_status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, "unpaid")',
- [username, phone, hashedPassword, role, 0, 'pending', city, district]
+ 'INSERT INTO users (username, phone, password, role, points, audit_status, city, district_id, payment_status, province, inviter) VALUES (?, ?, ?, ?, ?, ?, ?, ?, "unpaid", ?, ?)',
+ [username, phone, hashedPassword, role, 0, 'pending', city, district, province, inviter]
);
const userId = result.insertId;
-
- // 用户余额已在创建用户时设置为默认值0.00,无需额外操作
-
-
-
// 根据地区自动关联代理
const [agents] = await db.execute(
'SELECT ra.id FROM users u INNER JOIN regional_agents ra ON u.id = ra.user_id WHERE ra.region_id = ? AND ra.status = "active" ORDER BY ra.created_at ASC LIMIT 1',
@@ -237,7 +111,7 @@ router.post('/register', async (req, res) => {
});
} catch (error) {
try {
- await getDB().query('ROLLBACK');
+ // await getDB().query('ROLLBACK');
} catch (rollbackError) {
console.error('回滚错误:', rollbackError);
}
@@ -251,55 +125,7 @@ router.post('/register', async (req, res) => {
}
});
-/**
- * @swagger
- * /auth/login:
- * post:
- * summary: 用户登录
- * tags: [Authentication]
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * $ref: '#/components/schemas/LoginCredentials'
- * responses:
- * 200:
- * description: 登录成功
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * message:
- * type: string
- * token:
- * type: string
- * description: JWT认证令牌
- * user:
- * type: object
- * properties:
- * id:
- * type: integer
- * username:
- * type: string
- * role:
- * type: string
- * avatar:
- * type: string
- * points:
- * type: integer
- * 400:
- * description: 请求参数错误
- * 401:
- * description: 用户名或密码错误
- * 403:
- * description: 账户审核未通过
- * 500:
- * description: 服务器错误
- */
+
router.post('/login', async (req, res) => {
try {
const db = getDB();
diff --git a/routes/captcha.js b/routes/captcha.js
index 52f5985..8d3d64e 100644
--- a/routes/captcha.js
+++ b/routes/captcha.js
@@ -2,12 +2,7 @@ const express = require('express');
const crypto = require('crypto');
const router = express.Router();
-/**
- * @swagger
- * tags:
- * name: Captcha
- * description: 验证码API
- */
+
// 内存存储验证码(生产环境建议使用Redis)
@@ -107,35 +102,7 @@ function generateCaptchaSVG(text) {
return svg;
}
-/**
- * @swagger
- * /captcha/generate:
- * get:
- * summary: 生成图形验证码
- * tags: [Captcha]
- * responses:
- * 200:
- * description: 成功生成验证码
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * example: true
- * data:
- * type: object
- * properties:
- * captchaId:
- * type: string
- * description: 验证码唯一ID
- * image:
- * type: string
- * description: Base64编码的SVG验证码图片
- * 500:
- * description: 服务器错误
- */
+
router.get('/generate', (req, res) => {
try {
// 生成验证码文本
@@ -169,58 +136,7 @@ router.get('/generate', (req, res) => {
}
});
-/**
- * @swagger
- * /captcha/verify:
- * post:
- * summary: 验证用户输入的验证码
- * tags: [Captcha]
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * type: object
- * required:
- * - captchaId
- * - captchaText
- * properties:
- * captchaId:
- * type: string
- * description: 验证码唯一ID
- * captchaText:
- * type: string
- * description: 用户输入的验证码
- * responses:
- * 200:
- * description: 验证码验证成功
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * example: true
- * message:
- * type: string
- * example: 验证码验证成功
- * 400:
- * description: 验证码错误或已过期
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * example: false
- * message:
- * type: string
- * example: 验证码错误
- * 500:
- * description: 服务器错误
- */
+
router.post('/verify', (req, res) => {
try {
const { captchaId, captchaText } = req.body;
diff --git a/routes/matching.js b/routes/matching.js
index 9d1222d..456857f 100644
--- a/routes/matching.js
+++ b/routes/matching.js
@@ -4,133 +4,17 @@ const { getDB } = require('../database');
const matchingService = require('../services/matchingService');
const { auth } = require('../middleware/auth');
-/**
- * @swagger
- * tags:
- * name: Matching
- * description: 匹配订单相关接口
- */
-/**
- * @swagger
- * components:
- * schemas:
- * MatchingOrder:
- * type: object
- * properties:
- * id:
- * type: integer
- * description: 匹配订单ID
- * initiator_id:
- * type: integer
- * description: 发起人ID
- * matching_type:
- * type: string
- * enum: [small, large]
- * description: 匹配类型(小额或大额)
- * amount:
- * type: number
- * description: 匹配总金额
- * status:
- * type: string
- * enum: [pending, matching, completed, failed]
- * description: 订单状态
- * created_at:
- * type: string
- * format: date-time
- * description: 创建时间
- * Allocation:
- * type: object
- * properties:
- * id:
- * type: integer
- * description: 分配ID
- * from_user_id:
- * type: integer
- * description: 发送方用户ID
- * to_user_id:
- * type: integer
- * description: 接收方用户ID
- * amount:
- * type: number
- * description: 分配金额
- * cycle_number:
- * type: integer
- * description: 轮次编号
- * status:
- * type: string
- * enum: [pending, confirmed, rejected, cancelled]
- * description: 分配状态
- * created_at:
- * type: string
- * format: date-time
- * description: 创建时间
- */
-
-/**
- * @swagger
- * /api/matching/create:
- * post:
- * summary: 创建匹配订单
- * tags: [Matching]
- * security:
- * - bearerAuth: []
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * matchingType:
- * type: string
- * enum: [small, large]
- * default: small
- * description: 匹配类型(小额或大额)
- * customAmount:
- * type: number
- * description: 大额匹配时的自定义金额(5000-50000之间)
- * responses:
- * 200:
- * description: 匹配订单创建成功
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * message:
- * type: string
- * data:
- * type: object
- * properties:
- * matchingOrderId:
- * type: integer
- * amounts:
- * type: array
- * items:
- * type: number
- * matchingType:
- * type: string
- * totalAmount:
- * type: number
- * 400:
- * description: 参数错误或用户未满足匹配条件
- * 401:
- * description: 未授权
- * 404:
- * description: 用户不存在
- * 500:
- * description: 服务器错误
- */
router.post('/create', auth, async (req, res) => {
try {
console.log('匹配订单创建请求 - 用户ID:', req.user.id);
console.log('请求体:', req.body);
const userId = req.user.id;
const { matchingType = 'small', customAmount } = req.body;
-
+ const [user_type] = await getDB().query(`SELECT count(*) as total FROM users WHERE id=${userId} and user_type='directly_operated'`);
+ if(user_type[0].total > 0){
+ return res.status(400).json({message: '平台暂不支持直营用户获得融豆'})
+ }
// 验证匹配类型
if (!['small', 'large'].includes(matchingType)) {
return res.status(400).json({ message: '无效的匹配类型' });
@@ -141,8 +25,8 @@ router.post('/create', auth, async (req, res) => {
if (!customAmount || typeof customAmount !== 'number') {
return res.status(400).json({ message: '大额匹配需要指定金额' });
}
- if (customAmount < 5000 || customAmount > 50000) {
- return res.status(400).json({ message: '大额匹配金额必须在5000-50000之间' });
+ if (customAmount < 3000 || customAmount > 50000) {
+ return res.status(400).json({ message: '大额匹配金额必须在3000-50000之间' });
}
}
@@ -208,46 +92,7 @@ router.post('/create', auth, async (req, res) => {
}
});
-/**
- * @swagger
- * /api/matching/my-orders:
- * get:
- * summary: 获取用户的匹配订单列表
- * tags: [Matching]
- * security:
- * - bearerAuth: []
- * parameters:
- * - in: query
- * name: page
- * schema:
- * type: integer
- * default: 1
- * description: 页码
- * - in: query
- * name: limit
- * schema:
- * type: integer
- * default: 10
- * description: 每页数量
- * responses:
- * 200:
- * description: 成功获取匹配订单列表
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * data:
- * type: array
- * items:
- * $ref: '#/components/schemas/MatchingOrder'
- * 401:
- * description: 未授权
- * 500:
- * description: 服务器错误
- */
+
router.get('/my-orders', auth, async (req, res) => {
try {
const userId = req.user.id;
diff --git a/routes/sms.js b/routes/sms.js
index 5492867..5f0040d 100644
--- a/routes/sms.js
+++ b/routes/sms.js
@@ -106,7 +106,7 @@ function generateSMSCode() {
router.post('/send', async (req, res) => {
try {
const { phone } = req.body
-
+
// 验证手机号格式
const phoneRegex = /^1[3-9]\d{9}$/
if (!phoneRegex.test(phone)) {
@@ -115,7 +115,7 @@ router.post('/send', async (req, res) => {
message: '手机号格式不正确'
})
}
-
+
// 检查发送频率限制
const lastSendTime = smsCodeStore.get(`last_send_${phone}`)
if (lastSendTime && Date.now() - lastSendTime < SEND_INTERVAL) {
@@ -125,31 +125,38 @@ router.post('/send', async (req, res) => {
message: `请等待${remainingTime}秒后再发送`
})
}
-
+
// 生成6位数字验证码
const code = Math.random().toString().slice(-6)
-
+
// 存储验证码信息
smsCodeStore.set(phone, {
code,
timestamp: Date.now(),
attempts: 0
})
-
+
// 记录发送时间
smsCodeStore.set(`last_send_${phone}`, Date.now())
// 生产环境发送真实短信
try {
+ console.log(code);
+
+ res.json({
+ success: true,
+ message: '验证码发送成功'
+ })
+ return
const sendSmsRequest = new Dysmsapi20170525.SendSmsRequest({
phoneNumbers: phone,
signName: SMS_CONFIG.signName,
templateCode: SMS_CONFIG.templateCode,
templateParam: JSON.stringify({ code })
})
-
+
const response = await client.sendSms(sendSmsRequest)
- console.log(response.body);
-
+ console.log(response.body);
+
if (response.body.code === 'OK') {
res.json({
success: true,
@@ -169,7 +176,7 @@ router.post('/send', async (req, res) => {
message: '发送失败,请稍后重试'
})
}
-
+
} catch (error) {
console.error('发送短信验证码失败:', error)
res.status(500).json({
@@ -230,43 +237,43 @@ router.post('/send', async (req, res) => {
router.post('/verify', async (req, res) => {
try {
const { phone, code } = req.body;
-
+
if (!phone || !code) {
return res.status(400).json({ success: false, message: '手机号和验证码不能为空' });
}
-
+
const storedData = smsCodeStore.get(phone);
-
+
if (!storedData) {
return res.status(400).json({ success: false, message: '验证码不存在或已过期' });
}
-
+
// 检查验证码是否过期(5分钟)
if (Date.now() - storedData.timestamp > 300000) {
smsCodeStore.delete(phone);
return res.status(400).json({ success: false, message: '验证码已过期' });
}
-
+
// 检查尝试次数(最多3次)
if (storedData.attempts >= 3) {
smsCodeStore.delete(phone);
return res.status(400).json({ success: false, message: '验证码错误次数过多,请重新获取' });
}
-
+
// 验证验证码
if (storedData.code !== code) {
storedData.attempts++;
smsCodeStore.set(phone, storedData);
- return res.status(400).json({
- success: false,
- message: `验证码错误,还可尝试${3 - storedData.attempts}次`
+ return res.status(400).json({
+ success: false,
+ message: `验证码错误,还可尝试${3 - storedData.attempts}次`
});
}
-
+
// 验证成功,删除验证码
smsCodeStore.delete(phone);
smsCodeStore.delete(`time_${phone}`);
-
+
res.json({
success: true,
message: '手机号验证成功',
@@ -275,7 +282,7 @@ router.post('/verify', async (req, res) => {
verified: true
}
});
-
+
} catch (error) {
console.error('验证短信验证码错误:', error);
res.status(500).json({ success: false, message: '验证失败' });
@@ -290,30 +297,30 @@ router.post('/verify', async (req, res) => {
*/
function verifySMSCode(phone, code) {
const storedData = smsCodeStore.get(phone);
-
+
if (!storedData) {
return false;
}
-
+
// 检查是否过期
if (Date.now() - storedData.timestamp > 300000) {
smsCodeStore.delete(phone);
return false;
}
-
+
// 检查尝试次数
if (storedData.attempts >= 3) {
smsCodeStore.delete(phone);
return false;
}
-
+
// 验证验证码
if (storedData.code === code) {
smsCodeStore.delete(phone);
smsCodeStore.delete(`time_${phone}`);
return true;
}
-
+
return false;
}
@@ -322,7 +329,7 @@ setInterval(() => {
const now = Date.now();
for (const [key, value] of smsCodeStore.entries()) {
if (key.startsWith('time_')) continue;
-
+
if (value.timestamp && now - value.timestamp > 300000) {
smsCodeStore.delete(key);
smsCodeStore.delete(`time_${key}`);
diff --git a/routes/transfers.js b/routes/transfers.js
index 3c6c1e3..179ebe7 100644
--- a/routes/transfers.js
+++ b/routes/transfers.js
@@ -11,183 +11,16 @@ const dayjs = require('dayjs');
const router = express.Router();
-/**
- * @swagger
- * components:
- * schemas:
- * Transfer:
- * type: object
- * properties:
- * id:
- * type: integer
- * description: 转账记录ID
- * user_id:
- * type: integer
- * description: 用户ID
- * recipient_id:
- * type: integer
- * description: 接收方用户ID
- * amount:
- * type: number
- * format: float
- * description: 转账金额
- * status:
- * type: string
- * enum: [pending, completed, failed, cancelled]
- * description: 转账状态
- * transfer_type:
- * type: string
- * enum: [user_to_user, user_to_system, system_to_user]
- * description: 转账类型
- * voucher_image:
- * type: string
- * description: 转账凭证图片路径
- * remark:
- * type: string
- * description: 转账备注
- * created_at:
- * type: string
- * format: date-time
- * description: 创建时间
- * updated_at:
- * type: string
- * format: date-time
- * description: 更新时间
- * Pagination:
- * type: object
- * properties:
- * total:
- * type: integer
- * description: 总记录数
- * page:
- * type: integer
- * description: 当前页码
- * limit:
- * type: integer
- * description: 每页记录数
- * total_pages:
- * type: integer
- * description: 总页数
- */
-// 配置文件上传
-const storage = multer.diskStorage({
- destination: function (req, file, cb) {
- cb(null, 'uploads/')
- },
- filename: function (req, file, cb) {
- const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
- cb(null, 'voucher-' + uniqueSuffix + path.extname(file.originalname))
- }
-});
-
-const upload = multer({
- storage: storage,
- fileFilter: (req, file, cb) => {
- if (file.mimetype.startsWith('image/')) {
- cb(null, true);
- } else {
- cb(new Error('只允许上传图片文件'));
- }
- },
- limits: {
- fileSize: 5 * 1024 * 1024 // 5MB
- }
-});
-
-/**
- * @swagger
- * /transfers:
- * get:
- * summary: 获取转账列表
- * tags: [Transfers]
- * security:
- * - bearerAuth: []
- * parameters:
- * - in: query
- * name: status
- * schema:
- * type: string
- * description: 转账状态过滤
- * - in: query
- * name: transfer_type
- * schema:
- * type: string
- * description: 转账类型过滤
- * - in: query
- * name: start_date
- * schema:
- * type: string
- * format: date
- * description: 开始日期过滤
- * - in: query
- * name: end_date
- * schema:
- * type: string
- * format: date
- * description: 结束日期过滤
- * - in: query
- * name: search
- * schema:
- * type: string
- * description: 搜索关键词(用户名或真实姓名)
- * - in: query
- * name: page
- * schema:
- * type: integer
- * default: 1
- * description: 页码
- * - in: query
- * name: limit
- * schema:
- * type: integer
- * default: 10
- * description: 每页数量
- * - in: query
- * name: sort
- * schema:
- * type: string
- * description: 排序字段
- * - in: query
- * name: order
- * schema:
- * type: string
- * enum: [asc, desc]
- * description: 排序方向
- * responses:
- * 200:
- * description: 成功获取转账列表
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * data:
- * type: object
- * properties:
- * transfers:
- * type: array
- * items:
- * $ref: '#/components/schemas/Transfer'
- * pagination:
- * $ref: '#/components/schemas/Pagination'
- * 401:
- * description: 未授权
- * 500:
- * description: 服务器错误
- */
router.get('/',
authenticateToken,
validateQuery(transferSchemas.query),
async (req, res, next) => {
try {
- const { page, limit, status, transfer_type, start_date, end_date, search, sort, order } = req.query;
+ const { page, limit, status, start_date, end_date, search, sort, order } = req.query;
const filters = {
status,
- transfer_type,
start_date,
end_date,
search
@@ -216,84 +49,38 @@ router.get('/',
}
);
-/**
- * @swagger
- * /transfers/list:
- * get:
- * summary: 获取转账记录列表
- * tags: [Transfers]
- * security:
- * - bearerAuth: []
- * parameters:
- * - in: query
- * name: status
- * schema:
- * type: string
- * description: 转账状态过滤
- * - in: query
- * name: transfer_type
- * schema:
- * type: string
- * description: 转账类型过滤
- * - in: query
- * name: start_date
- * schema:
- * type: string
- * format: date
- * description: 开始日期过滤
- * - in: query
- * name: end_date
- * schema:
- * type: string
- * format: date
- * description: 结束日期过滤
- * - in: query
- * name: page
- * schema:
- * type: integer
- * default: 1
- * description: 页码
- * - in: query
- * name: limit
- * schema:
- * type: integer
- * default: 10
- * description: 每页数量
- * - in: query
- * name: sort
- * schema:
- * type: string
- * description: 排序字段
- * - in: query
- * name: order
- * schema:
- * type: string
- * enum: [asc, desc]
- * description: 排序方向
- * responses:
- * 200:
- * description: 成功获取转账记录列表
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * data:
- * type: object
- * properties:
- * transfers:
- * type: array
- * items:
- * $ref: '#/components/schemas/Transfer'
- * pagination:
- * $ref: '#/components/schemas/Pagination'
- * 401:
- * description: 未授权
- * 500:
- * description: 服务器错误
- */
+router.get('/history',authenticateToken,async (req, res, next) => {
+ try {
+ const { page, limit, start_date, end_date, search, sort, order } = req.query;
+
+ const filters = {
+ start_date,
+ end_date,
+ search
+ };
+
+ // 非管理员只能查看自己相关的转账
+ if (req.user.role !== 'admin') {
+ filters.user_id = req.user.id;
+ }
+
+ const result = await transferService.getTransfersHistory(filters, { page, limit, sort, order });
+
+ logger.info('Transfer list requested', {
+ userId: req.user.id,
+ filters,
+ resultCount: result.transfers.length
+ });
+
+ res.json({
+ success: true,
+ data: result
+ });
+ } catch (error) {
+ next(error);
+ }
+})
+
router.get('/list',
authenticateToken,
validateQuery(transferSchemas.query),
@@ -331,49 +118,7 @@ router.get('/list',
}
);
-/**
- * @swagger
- * /transfers/public-account:
- * get:
- * summary: 获取公户信息
- * tags: [Transfers]
- * security:
- * - bearerAuth: []
- * responses:
- * 200:
- * description: 成功获取公户信息
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * example: true
- * data:
- * type: object
- * properties:
- * id:
- * type: integer
- * description: 公户ID
- * username:
- * type: string
- * description: 公户用户名
- * example: public_account
- * real_name:
- * type: string
- * description: 公户名称
- * balance:
- * type: number
- * format: float
- * description: 公户余额
- * 401:
- * description: 未授权
- * 404:
- * description: 公户不存在
- * 500:
- * description: 服务器错误
- */
+
router.get('/public-account', authenticateToken, async (req, res) => {
try {
const db = getDB();
@@ -394,66 +139,7 @@ router.get('/public-account', authenticateToken, async (req, res) => {
}
});
-/**
- * @swagger
- * /transfers/create:
- * post:
- * summary: 创建转账记录
- * tags: [Transfers]
- * security:
- * - bearerAuth: []
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * type: object
- * required:
- * - to_user_id
- * - amount
- * - transfer_type
- * properties:
- * to_user_id:
- * type: integer
- * description: 接收方用户ID
- * amount:
- * type: number
- * format: float
- * description: 转账金额
- * transfer_type:
- * type: string
- * enum: [user_to_user, user_to_system, system_to_user]
- * description: 转账类型
- * remark:
- * type: string
- * description: 转账备注
- * responses:
- * 201:
- * description: 转账记录创建成功
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * example: true
- * message:
- * type: string
- * example: 转账记录创建成功,等待确认
- * data:
- * type: object
- * properties:
- * transfer_id:
- * type: integer
- * description: 转账记录ID
- * 400:
- * description: 请求参数错误
- * 401:
- * description: 未授权
- * 500:
- * description: 服务器错误
- */
+
router.post('/create',
authenticateToken,
validate(transferSchemas.create),
@@ -478,72 +164,7 @@ router.post('/create',
}
);
-/**
- * @swagger
- * /transfers/admin/create:
- * post:
- * summary: 管理员创建转账记录
- * tags: [Transfers]
- * security:
- * - bearerAuth: []
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * type: object
- * required:
- * - from_user_id
- * - to_user_id
- * - amount
- * - transfer_type
- * properties:
- * from_user_id:
- * type: integer
- * description: 发送方用户ID
- * to_user_id:
- * type: integer
- * description: 接收方用户ID
- * amount:
- * type: number
- * format: float
- * description: 转账金额
- * transfer_type:
- * type: string
- * enum: [user_to_user, user_to_system, system_to_user]
- * description: 转账类型
- * description:
- * type: string
- * description: 转账描述
- * responses:
- * 201:
- * description: 转账记录创建成功
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * example: true
- * message:
- * type: string
- * example: 转账记录创建成功
- * data:
- * type: object
- * properties:
- * transfer_id:
- * type: integer
- * description: 转账记录ID
- * 400:
- * description: 请求参数错误
- * 401:
- * description: 未授权
- * 403:
- * description: 权限不足
- * 500:
- * description: 服务器错误
- */
+
router.post('/admin/create',
authenticateToken,
async (req, res, next) => {
diff --git a/routes/upload.js b/routes/upload.js
index 365f5f9..2222e0b 100644
--- a/routes/upload.js
+++ b/routes/upload.js
@@ -9,7 +9,7 @@ const { initializeBuckets } = require('../config/minio');
const router = express.Router();
// 初始化MinIO存储桶
-initializeBuckets().catch(console.error);
+// initializeBuckets().catch(console.error);
/**
* @swagger
diff --git a/routes/users.js b/routes/users.js
index 9d18bd8..60c2f13 100644
--- a/routes/users.js
+++ b/routes/users.js
@@ -13,127 +13,7 @@ const router = express.Router();
* description: 用户管理API
*/
-/**
- * @swagger
- * components:
- * schemas:
- * User:
- * type: object
- * required:
- * - username
- * - password
- * - real_name
- * - id_card
- * properties:
- * id:
- * type: integer
- * description: 用户ID
- * username:
- * type: string
- * description: 用户名
- * role:
- * type: string
- * description: 用户角色
- * enum: [user, admin, merchant]
- * avatar:
- * type: string
- * description: 用户头像URL
- * points:
- * type: integer
- * description: 用户积分
- * real_name:
- * type: string
- * description: 真实姓名
- * id_card:
- * type: string
- * description: 身份证号
- * phone:
- * type: string
- * description: 手机号
- * is_system_account:
- * type: boolean
- * description: 是否为系统账户
- * is_distribute:
- * type: boolean
- * description: 是否为分发账户
- * created_at:
- * type: string
- * format: date-time
- * description: 创建时间
- * updated_at:
- * type: string
- * format: date-time
- * description: 更新时间
- */
-/**
- * @swagger
- * /users:
- * post:
- * summary: 创建用户(管理员权限)
- * tags: [Users]
- * security:
- * - bearerAuth: []
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * type: object
- * required:
- * - username
- * - password
- * - real_name
- * - id_card
- * properties:
- * username:
- * type: string
- * password:
- * type: string
- * role:
- * type: string
- * enum: [user, admin, merchant]
- * default: user
- * is_system_account:
- * type: boolean
- * default: false
- * real_name:
- * type: string
- * id_card:
- * type: string
- * wechat_qr:
- * type: string
- * alipay_qr:
- * type: string
- * bank_card:
- * type: string
- * unionpay_qr:
- * type: string
- * phone:
- * type: string
- * responses:
- * 201:
- * description: 用户创建成功
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * message:
- * type: string
- * user:
- * $ref: '#/components/schemas/User'
- * 400:
- * description: 请求参数错误
- * 401:
- * description: 未授权
- * 403:
- * description: 权限不足
- * 500:
- * description: 服务器错误
- */
router.post('/', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
@@ -150,7 +30,13 @@ router.post('/', auth, adminAuth, async (req, res) => {
alipayQr,
bankCard,
unionpayQr,
- phone
+ province,
+ city,
+ districtId,
+ phone,
+ avatar,
+ user_type = 'directly_operated',
+ inviter = ''
} = req.body;
if (!username || !password) {
@@ -160,6 +46,9 @@ router.post('/', auth, adminAuth, async (req, res) => {
if (!realName || !idCard) {
return res.status(400).json({ success: false, message: '姓名和身份证号不能为空' });
}
+ if(!city || !districtId || !province){
+ return res.status(400).json({ success: false, message: '请选择城市和区县' });
+ }
// 验证身份证号格式
const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
@@ -181,12 +70,21 @@ router.post('/', auth, adminAuth, async (req, res) => {
const hashedPassword = await bcrypt.hash(password, 10);
// 创建用户
+ console.log([username, hashedPassword, role, isSystemAccount, 0, realName, idCard, wechatQr, alipayQr, bankCard, unionpayQr, phone, province, city, districtId, user_type, inviter],'info');
+
const [result] = await db.execute(
- 'INSERT INTO users (username, password, role, is_system_account, points, real_name, id_card, wechat_qr, alipay_qr, bank_card, unionpay_qr, phone) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
- [username, hashedPassword, role, isSystemAccount, 0, realName, idCard, wechatQr, alipayQr, bankCard, unionpayQr, phone]
+ 'INSERT INTO users (username, password, role, is_system_account, points, real_name, id_card, wechat_qr, alipay_qr, bank_card, unionpay_qr, phone, province, city, district_id, user_type, inviter,avatar) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?,?)',
+ [username, hashedPassword, role, isSystemAccount, 0, realName, idCard, wechatQr, alipayQr, bankCard, unionpayQr, phone, province, city, districtId, user_type, inviter,avatar]
);
const userId = result.insertId;
+ if(user_type === 'agent_directly'){
+ const agentCode = 'AG' + Date.now().toString().slice(-8);
+ await db.execute(
+ 'INSERT INTO regional_agents (user_id, region_id,status,agent_code) VALUES (?, ?,?,?)',
+ [userId, districtId,'active',agentCode]
+ );
+ }
// 用户余额已在创建用户时设置为默认值0.00,无需额外操作
@@ -339,7 +237,17 @@ router.get('/for-transfer', auth, async (req, res) => {
router.get('/', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
- const { page = 1, limit = 10, search = '', role = '', city = '', district = '', sort = 'created_at', order = 'desc' } = req.query;
+ const {
+ page = 1,
+ limit = 10,
+ search = '',
+ role = '',
+ city = '',
+ district = '',
+ province = '',
+ sort = 'created_at',
+ order = 'desc'
+ } = req.query;
// 确保参数为有效数字
const pageNum = Math.max(1, parseInt(page) || 1);
@@ -369,6 +277,12 @@ router.get('/', auth, adminAuth, async (req, res) => {
listParams.push(city);
}
+ if (province) {
+ whereConditions.push('u.province = ?');
+ countParams.push(province);
+ listParams.push(province);
+ }
+
if (district) {
whereConditions.push('u.district_id = ?');
countParams.push(district);
@@ -400,36 +314,66 @@ router.get('/', auth, adminAuth, async (req, res) => {
// 获取用户列表,关联地区信息和转账统计
const [users] = await db.execute(
- `SELECT u.id, u.username, u.role, u.avatar, u.points, u.balance, u.real_name, u.id_card, u.phone,
- u.wechat_qr, u.alipay_qr, u.bank_card, u.unionpay_qr, u.audit_status, u.is_system_account,
- u.created_at, u.updated_at, u.city, u.district_id,u.id_card_front,u.id_card_back,
- u.business_license,u.is_distribute,
- r.city_name, r.district_name,
- COALESCE(yesterday_out.amount, 0) as yesterday_transfer_amount,
- COALESCE(today_in.amount, 0) as today_received_amount
- FROM users u
- LEFT JOIN zhejiang_regions r ON u.district_id = r.id
- LEFT JOIN (
- SELECT from_user_id, SUM(amount) as amount
- FROM transfers
- WHERE created_at >= DATE(DATE_SUB(NOW(), INTERVAL 1 DAY))
- AND created_at < DATE(NOW())
- AND status IN ('confirmed', 'received')
- GROUP BY from_user_id
- ) yesterday_out ON u.id = yesterday_out.from_user_id
- LEFT JOIN (
- SELECT to_user_id, SUM(amount) as amount
- FROM transfers
- WHERE created_at >= DATE(NOW())
- AND created_at < DATE(DATE_ADD(NOW(), INTERVAL 1 DAY))
- AND status IN ('confirmed', 'received')
- GROUP BY to_user_id
- ) today_in ON u.id = today_in.to_user_id
- ${whereClause}
- ORDER BY u.${sortField} ${sortOrder}
- LIMIT ${limitNum} OFFSET ${offset}`,
+ `SELECT
+ u.id,
+ u.username,
+ u.role,
+ u.avatar,
+ u.points,
+ u.balance,
+ u.real_name,
+ u.id_card,
+ u.phone,
+ u.wechat_qr,
+ u.alipay_qr,
+ u.bank_card,
+ u.unionpay_qr,
+ u.audit_status,
+ u.is_system_account,
+ u.created_at,
+ u.updated_at,
+ u.province,
+ u.city,
+ u.district_id,
+ u.id_card_front,
+ u.id_card_back,
+ u.business_license,
+ u.is_distribute,
+ u.user_type,
+ u.inviter,
+ p.name as province_name,
+ c.name as city_name,
+ d.name as district_name,
+ COALESCE(yesterday_out.amount, 0) as yesterday_transfer_amount,
+ COALESCE(today_in.amount, 0) as today_received_amount
+ FROM users u
+ LEFT JOIN china_regions p ON u.province = p.code
+ LEFT JOIN china_regions c ON u.city = c.code
+ LEFT JOIN china_regions d ON u.district_id = d.code
+ LEFT JOIN (
+ SELECT from_user_id, SUM(amount) as amount
+ FROM transfers
+ WHERE created_at >= DATE(DATE_SUB(NOW(), INTERVAL 1 DAY))
+ AND created_at < DATE(NOW())
+ AND status IN ('confirmed', 'received')
+ GROUP BY from_user_id
+ ) yesterday_out ON u.id = yesterday_out.from_user_id
+ LEFT JOIN (
+ SELECT to_user_id, SUM(amount) as amount
+ FROM transfers
+ WHERE created_at >= DATE(NOW())
+ AND created_at < DATE(DATE_ADD(NOW(), INTERVAL 1 DAY))
+ AND status IN ('confirmed', 'received')
+ GROUP BY to_user_id
+ ) today_in ON u.id = today_in.to_user_id
+ ${whereClause}
+ ORDER BY u.${sortField} ${sortOrder}
+ LIMIT ${limitNum} OFFSET ${offset}`,
listParams.slice(0, -2)
);
+ users.forEach(user => {
+ user.region = [user.province, user.city, user.district_id]
+ })
res.json({
success: true,
@@ -891,247 +835,6 @@ router.get('/daily-revenue', auth, adminAuth, async (req, res) => {
res.status(500).json({ success: false, message: '获取日收入统计失败' });
}
});
-
-// 生成注册码(管理员权限)==================== 激活码管理 ====================
-
-/**
- * 生成激活码(管理员权限)
- */
-router.post('/registration-codes', auth, adminAuth, async (req, res) => {
- try {
- const db = getDB();
- const adminId = req.user.id;
-
- // 生成6位随机激活码
- const crypto = require('crypto');
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
- let code = '';
- for (let i = 0; i < 6; i++) {
- code += chars.charAt(Math.floor(Math.random() * chars.length));
- }
-
- // 设置过期时间为1小时后
- const expiresAt = req.body.expiresAt || new Date(Date.now() + 60 * 60 * 1000);
-
- // 插入激活码
- const [result] = await db.execute(
- 'INSERT INTO registration_codes (code, expires_at, created_by_admin_id) VALUES (?, ?, ?)',
- [code, expiresAt, adminId]
- );
-
- res.status(201).json({
- success: true,
- message: '激活码生成成功',
- data: {
- id: result.insertId,
- code,
- expiresAt,
- createdAt: new Date()
- }
- });
- } catch (error) {
- console.error('生成激活码错误:', error);
- res.status(500).json({ success: false, message: '生成激活码失败' });
- }
-});
-
-/**
- * 批量生成激活码(管理员权限)
- */
-router.post('/registration-codes/batch', auth, adminAuth, async (req, res) => {
- try {
- const db = getDB();
- const adminId = req.user.id;
- const { count = 1 } = req.body;
-
- // 验证参数
- const codeCount = Math.max(1, Math.min(100, parseInt(count) || 1));
-
- const crypto = require('crypto');
- const codes = [];
- const values = [];
- const expiresAt = req.body.expiresAt || new Date(Date.now() + 60 * 60 * 1000);
-
- // 生成指定数量的激活码
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
- for (let i = 0; i < codeCount; i++) {
- let code = '';
- for (let j = 0; j < 6; j++) {
- code += chars.charAt(Math.floor(Math.random() * chars.length));
- }
- codes.push(code);
- values.push(code, expiresAt, adminId);
- }
-
- // 批量插入数据库
- const placeholders = Array(codeCount).fill('(?, ?, ?)').join(', ');
-
- await db.execute(
- `INSERT INTO registration_codes (code, expires_at, created_by_admin_id) VALUES ${placeholders}`,
- values
- );
-
- res.status(201).json({
- success: true,
- message: `成功生成 ${codeCount} 个激活码`,
- data: {
- codes,
- count: codeCount,
- expiresAt,
- }
- });
- } catch (error) {
- console.error('批量生成激活码错误:', error);
- res.status(500).json({ success: false, message: '批量生成激活码失败' });
- }
-});
-
-/**
- * 获取激活码列表(管理员权限)
- */
-router.get('/registration-codes', auth, adminAuth, async (req, res) => {
- try {
- const db = getDB();
- const { page = 1, limit = 20, status, keyword, sort = 'created_at', order = 'desc' } = req.query;
- const offset = (page - 1) * limit;
-
- let whereClause = '';
- let whereConditions = [];
- let countParams = [];
- let listParams = [];
-
- // 根据状态筛选
- if (status === 'unused') {
- whereConditions.push('rc.is_used = FALSE AND rc.expires_at > NOW()');
- } else if (status === 'used') {
- whereConditions.push('rc.is_used = TRUE');
- } else if (status === 'expired') {
- whereConditions.push('rc.is_used = FALSE AND rc.expires_at <= NOW()');
- }
-
- // 关键词搜索
- if (keyword) {
- whereConditions.push(`rc.code LIKE '%${keyword}%'`);
- }
-
- // 构建WHERE子句
- if (whereConditions.length > 0) {
- whereClause = 'WHERE ' + whereConditions.join(' AND ');
- }
-
- // 处理排序参数
- const allowedSortFields = ['created_at', 'expires_at', 'used_at', 'code', 'status'];
- const allowedOrders = ['asc', 'desc'];
-
- let sortField = 'rc.created_at';
- let sortOrder = 'DESC';
-
- if (allowedSortFields.includes(sort)) {
- if (sort === 'status') {
- // 状态字段需要使用CASE表达式
- sortField = `CASE
- WHEN rc.is_used = TRUE THEN 'used'
- WHEN rc.expires_at <= NOW() THEN 'expired'
- ELSE 'unused'
- END`;
- } else {
- sortField = `rc.${sort}`;
- }
- }
-
- if (allowedOrders.includes(order.toLowerCase())) {
- sortOrder = order.toUpperCase();
- }
-
- // 设置查询参数(MySQL驱动需要字符串形式的LIMIT和OFFSET)
- const limitStr = String(parseInt(limit));
- const offsetStr = String(parseInt(offset));
- listParams = [limitStr, offsetStr];
- countParams = [];
-
- // 获取激活码列表
- const [codes] = await db.execute(`
- SELECT
- rc.id,
- rc.code,
- rc.created_at,
- rc.expires_at,
- rc.used_at,
- rc.is_used,
- admin.username as created_by_admin,
- user.username as used_by_user,
- CASE
- WHEN rc.is_used = TRUE THEN 'used'
- WHEN rc.expires_at <= NOW() THEN 'expired'
- ELSE 'unused'
- END as status
- FROM registration_codes rc
- LEFT JOIN users admin ON rc.created_by_admin_id = admin.id
- LEFT JOIN users user ON rc.used_by_user_id = user.id
- ${whereClause}
- ORDER BY ${sortField} ${sortOrder}
- LIMIT ${limit} OFFSET ${offset}
- `, countParams);
-
- // 获取总数
- const [countResult] = await db.execute(`
- SELECT COUNT(*) as total
- FROM registration_codes rc
- ${whereClause}
- `, countParams);
-
- const total = countResult[0].total;
-
- res.json({
- success: true,
- data: {
- codes,
- pagination: {
- page: parseInt(page),
- limit: parseInt(limit),
- total,
- pages: Math.ceil(total / limit)
- }
- }
- });
- } catch (error) {
- console.error('获取激活码列表错误:', error);
- res.status(500).json({ success: false, message: '获取激活码列表失败' });
- }
-});
-
-/**
- * 删除激活码(管理员权限)
- */
-router.delete('/registration-codes/:id', auth, adminAuth, async (req, res) => {
- try {
- const db = getDB();
- const codeId = req.params.id;
-
- // 检查激活码是否存在
- const [codes] = await db.execute(
- 'SELECT id, is_used FROM registration_codes WHERE id = ?',
- [codeId]
- );
-
- if (codes.length === 0) {
- return res.status(404).json({ success: false, message: '激活码不存在' });
- }
-
- // 不能删除已使用的激活码
- if (codes[0].is_used) {
- return res.status(400).json({ success: false, message: '不能删除已使用的激活码' });
- }
-
- // 删除激活码
- await db.execute('DELETE FROM registration_codes WHERE id = ?', [codeId]);
-
- res.json({ success: true, message: '激活码删除成功' });
- } catch (error) {
- console.error('删除激活码错误:', error);
- res.status(500).json({ success: false, message: '删除激活码失败' });
- }
-});
// 获取当前用户个人资料
router.get('/profile', auth, async (req, res) => {
try {
@@ -1425,11 +1128,14 @@ router.put('/:id', auth, async (req, res) => {
alipayQr,
bankCard,
unionpayQr,
- city,
- districtId,
idCardFront,
idCardBack,
businessLicense,
+ province,
+ city,
+ districtId,
+ user_type,
+ inviter,
} = req.body;
// 只有管理员或用户本人可以更新信息
@@ -1571,11 +1277,26 @@ router.put('/:id', auth, async (req, res) => {
updateValues.push(idCardBack);
needsReaudit = true;
}
+ if (province !== undefined) {
+ updateFields.push('province = ?');
+ updateValues.push(province);
+ needsReaudit = true;
+ }
if (businessLicense !== undefined) {
updateFields.push('business_license = ?');
updateValues.push(businessLicense);
needsReaudit = true;
}
+ if (user_type !== undefined) {
+ updateFields.push('user_type = ?');
+ updateValues.push(user_type);
+ needsReaudit = true;
+ }
+ if (inviter !== undefined) {
+ updateFields.push('inviter = ?');
+ updateValues.push(inviter);
+ needsReaudit = true;
+ }
// 如果更新了关键信息且用户不是管理员,则重置审核状态为待审核
if (needsReaudit && req.user.role !== 'admin') {
@@ -1596,7 +1317,7 @@ router.put('/:id', auth, async (req, res) => {
// 返回更新后的用户信息
const [updatedUsers] = await db.execute(
- 'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, city, district_id, created_at, updated_at FROM users WHERE id = ?',
+ 'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, city, district_id, province, created_at, updated_at FROM users WHERE id = ?',
[userId]
);
@@ -1725,93 +1446,7 @@ router.get('/:id/audit-detail', auth, adminAuth, async (req, res) => {
res.status(500).json({ success: false, message: '获取用户审核详情失败' });
}
});
-/**
- * @swagger
- * /api/users/{id}/distribute:
- * put:
- * summary: 设置用户分发状态
- * description: 更新指定用户的分发状态
- * tags: [Users]
- * security:
- * - bearerAuth: []
- * parameters:
- * - in: path
- * name: id
- * required: true
- * schema:
- * type: integer
- * description: 用户ID
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * type: object
- * required:
- * - is_distribute
- * properties:
- * is_distribute:
- * type: boolean
- * description: 分发状态,true为启用分发,false为禁用分发
- * example: true
- * responses:
- * 200:
- * description: 分发状态更新成功
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * example: true
- * message:
- * type: string
- * example: "分发状态更新成功"
- * is_distribute:
- * type: boolean
- * description: 更新后的分发状态
- * example: true
- * 400:
- * description: 请求参数错误
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * example: false
- * message:
- * type: string
- * example: "分发状态无效"
- * 404:
- * description: 用户不存在
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * example: false
- * message:
- * type: string
- * example: "用户不存在"
- * 500:
- * description: 服务器内部错误
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * success:
- * type: boolean
- * example: false
- * message:
- * type: string
- * example: "服务器内部错误"
- */
+
router.put('/:id/distribute', auth, async (req, res) => {
try {
const db = getDB();
@@ -1822,30 +1457,335 @@ router.put('/:id/distribute', auth, async (req, res) => {
return res.status(400).json({ success: false, message: '分发状态无效' });
}
+
// 检查用户是否存在
const [users] = await db.execute(
- 'SELECT id FROM users WHERE id = ?',
+ 'SELECT id,user_type FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
return res.status(404).json({ success: false, message: '用户不存在' });
}
-
- // 更新分发状态
- await db.execute(
- 'UPDATE users SET is_distribute = ? WHERE id = ?',
- [is_distribute, userId]
- );
-
- res.json({
- success: true,
- message: '分发状态更新成功',
- is_distribute
- });
+ if(users[0].user_type === 'directly_operated'){
+ return res.status(400).json({ success: false, message: '直营用户不允许开启委托出售' });
+ }
+ let [isServiceFee] = await db.execute('SELECT COUNT(*) AS total FROM distribution WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 YEAR) AND user_id = ?', [userId]);
+ if (isServiceFee[0].total > 0) {
+ // 更新分发状态
+ await db.execute(
+ 'UPDATE users SET is_distribute = ? WHERE id = ?',
+ [is_distribute, userId]
+ );
+ res.json({
+ success: true,
+ message: '分发状态更新成功',
+ is_distribute
+ });
+ } else {
+ return res.json({ success: false, message: '请缴纳2980融豆服务费' });
+ }
} catch (error) {
}
})
+/**
+ * 扣除2980融豆服务费
+*/
+router.post('/:id/deduct-service-fee', auth, async (req, res) => {
+ const db = getDB();
+ try {
+ const userId = req.params.id;
+ const serviceFee = 2980; // 服务费金额
+
+ // 开始事务
+ await db.query('START TRANSACTION');
+
+ // 使用行级锁定查询用户信息,只锁定当前用户记录
+ const [users] = await db.execute(
+ 'SELECT id, balance, username FROM users WHERE id = ? FOR UPDATE',
+ [userId]
+ );
+
+ if (users.length === 0) {
+ await db.query('ROLLBACK');
+ return res.status(404).json({ success: false, message: '用户不存在' });
+ }
+ //判断今年是否已扣款
+ let [isServiceFee] = await db.execute('SELECT COUNT(*) AS total FROM distribution WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 YEAR) AND user_id = ?', [userId]);
+ if (isServiceFee[0].total > 0) {
+ return res.status(400).json({ success: false, message: '已缴纳2980融豆服务费' });
+ }
+ const user = users[0];
+ const currentBalance = Math.abs(user.balance); // 将负数转为正数处理
+
+ // 检查融豆余额是否足够
+ if (currentBalance < serviceFee) {
+ await db.query('ROLLBACK');
+ return res.status(400).json({
+ success: false,
+ message: `融豆余额不足,当前余额:${currentBalance},需要:${serviceFee}`
+ });
+ }
+
+ // 扣除融豆(balance字段为负数,所以减去服务费实际是增加负数)
+ await db.execute(
+ 'UPDATE users SET balance = balance + ? WHERE id = ?',
+ [serviceFee, userId]
+ );
+ //查找上级分销
+ let [distribute] = await db.execute(
+ 'SELECT inviter FROM users WHERE id = ?',
+ [userId]
+ );
+ distribute = distribute[0]
+ //如果有上级分销
+ if (distribute.inviter) {
+ // 查找上级分销
+ let [distributeUser] = await db.execute(
+ 'SELECT id, balance,user_type,inviter FROM users WHERE id = ?',
+ [distribute.inviter]
+ );
+ distributeUser = distributeUser[0]
+ if (distributeUser.user_type == 'agent') {
+ //给代理添加2980融豆的70%
+ await db.execute(
+ 'UPDATE users SET balance = balance - ? WHERE id = ?',
+ [serviceFee * 0.7, distributeUser.id]
+ );
+ //记录转账记录
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, distributeUser.id, 'user_to_agent', 'received', serviceFee * 0.7, '用户服务费返现', 'agent']
+ );
+ //记录平台利润
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, 3512, 'user_to_system', 'received', serviceFee * 0.3, '用户服务费返现', 'system']
+ );
+ //记录服务费
+ await db.execute(
+ 'INSERT INTO distribution (user_id,agent_id, amount, type) VALUES (?, ?, ?,?)',
+ [userId, distributeUser.id, serviceFee, 'agent']
+ )
+ }
+ //如果不是代理,查看是否是直营代理
+ if (distributeUser.user_type == 'agent_directly') {
+ //给直营代理50%融豆给平台50%融豆
+ await db.execute(
+ 'UPDATE regional_agents SET balance = balance - ? WHERE id = ?',
+ [serviceFee * 0.5, distributeUser.id]
+ );
+ //记录转账记录
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, distributeUser.id, 'user_to_agent', 'received', serviceFee * 0.5, '用户服务费返现', 'agent']
+ );
+ //记录平台利润
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, 3512, 'user_to_system', 'received', serviceFee * 0.5, '用户服务费返现', 'system']
+ );
+ //记录服务费
+ await db.execute(
+ 'INSERT INTO distribution (user_id,agent_id, amount, type) VALUES (?, ?, ?,?)',
+ [userId, distributeUser.id, serviceFee, 'direct_agent']
+ )
+ }
+ //是否是直营
+ if (distributeUser.user_type == 'directly_operated') {
+ //查询这个月直营做了多少单
+ let [orderCount] = await db.execute(
+ `SELECT COUNT(*) AS total FROM distribution WHERE agent_id = ? AND created_at >= DATE_FORMAT(NOW(), '%Y-%m-01')`,
+ [distributeUser.id]
+ );
+ orderCount = orderCount[0]
+ if (orderCount.total <= 5) {
+ //给直营代理20%融豆给平台50%融豆给用户30%
+ await db.execute(
+ 'UPDATE users SET balance = balance - ? WHERE id = ?',
+ [serviceFee * 0.2, distributeUser.inviter]
+ );
+ //给直营添加30%融豆
+ await db.execute(
+ 'UPDATE users SET balance = balance + ? WHERE id = ?',
+ [serviceFee * 0.3, distributeUser.id]
+ );
+ //记录转账记录
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, distributeUser.inviter, 'user_to_agent', 'received', serviceFee * 0.2, '用户服务费返现', 'operated_agent']
+ );
+ //记录直营利润
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, distributeUser.id, 'user_to_operated', 'received', serviceFee * 0.3, '用户服务费返现', 'directly_operated']
+ );
+ //记录平台利润
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, 3512, 'user_to_system', 'received', serviceFee * 0.5, '用户服务费返现', 'system']
+ );
+ }
+ if (orderCount.total > 5 && orderCount.total <= 15) {
+ //给直营代理20%融豆给平台50%融豆给用户30%
+ await db.execute(
+ 'UPDATE users SET balance = balance - ? WHERE id = ?',
+ [serviceFee * 0.15, distributeUser.inviter]
+ );
+ //给直营添加30%融豆
+ await db.execute(
+ 'UPDATE users SET balance = balance + ? WHERE id = ?',
+ [serviceFee * 0.35, distributeUser.id]
+ );
+ //记录转账记录
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, distributeUser.inviter, 'user_to_agent', 'received', serviceFee * 0.2, '用户服务费返现', 'operated_agent']
+ );
+ //记录直营利润
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, distributeUser.id, 'user_to_operated', 'received', serviceFee * 0.3, '用户服务费返现', 'directly_operated']
+ );
+ //记录平台利润
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, 3512, 'user_to_system', 'received', serviceFee * 0.5, '用户服务费返现', 'system']
+ );
+ }
+ if (orderCount.total > 15) {
+ //给直营代理20%融豆给平台50%融豆给用户30%
+ await db.execute(
+ 'UPDATE users SET balance = balance - ? WHERE id = ?',
+ [serviceFee * 0.1, distributeUser.inviter]
+ );
+ //给直营添加30%融豆
+ await db.execute(
+ 'UPDATE users SET balance = balance + ? WHERE id = ?',
+ [serviceFee * 0.4, distributeUser.id]
+ );
+ //记录转账记录
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, distributeUser.inviter, 'user_to_agent', 'received', serviceFee * 0.2, '用户服务费返现', 'operated_agent']
+ );
+ //记录直营利润
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, distributeUser.id, 'user_to_operated', 'received', serviceFee * 0.3, '用户服务费返现', 'directly_operated']
+ );
+ //记录平台利润
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, 3512, 'user_to_system', 'received', serviceFee * 0.5, '用户服务费返现', 'system']
+ );
+ }
+ //记录服务费
+ await db.execute(
+ 'INSERT INTO distribution (user_id,agent_id, amount, type) VALUES (?, ?, ?,?)',
+ [userId, distributeUser.id, serviceFee, 'direct_agent']
+ )
+ }
+ //要是用户之间分销
+ if (distributeUser.user_type == 'user') {
+ //查询用户是否有上级
+ let [userUpInfo] = await db.execute(
+ `SELECT * FROM users WHERE id = ?`,
+ [distributeUser.inviter]
+ )
+ userUpInfo = userUpInfo[0]
+ //判断用户上级是否是代理
+ if (userUpInfo && userUpInfo.user_type === 'agent') {
+ //给用户分配
+ await db.execute(
+ 'UPDATE users SET balance = balance - ? WHERE id = ?',
+ [serviceFee * 0.2, distributeUser.id]
+ );
+ //给代理分配
+ await db.execute(
+ 'UPDATE users SET balance = balance - ? WHERE id = ?',
+ [serviceFee * 0.5, userUpInfo.id]
+ );
+ //记录用户转账信息
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, distributeUser.id, 'user_to_user', 'received', serviceFee * 0.2, '用户服务费返现', 'manual']
+ );
+ //记录代理转账信息
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, userUpInfo.id, 'user_to_agent', 'received', serviceFee * 0.5, '用户服务费返现', 'manual']
+ );
+ //记录平台利润
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, 3512, 'user_to_system', 'received', serviceFee * 0.3, '用户服务费返现', 'system']
+ );
+ } else {
+ //用户没有上级
+ await db.execute(
+ 'UPDATE users SET balance = balance - ? WHERE id = ?',
+ [serviceFee * 0.2, distributeUser.id]
+ );
+ //记录转账记录
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, distributeUser.id, 'user_to_agent', 'received', serviceFee * 0.2, '用户服务费返现', 'manual']
+ );
+ //记录平台利润
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, 3512, 'user_to_system', 'received', serviceFee * 0.8, '用户服务费返现', 'system']
+ );
+
+ }
+ //记录服务费
+ await db.execute(
+ 'INSERT INTO distribution (user_id,agent_id, amount, type) VALUES (?, ?, ?,?)',
+ [userId, distributeUser.id, serviceFee, 'user']
+ )
+ }
+
+ } else {
+ //记录平台利润
+ await db.execute(
+ 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)',
+ [userId, 3512, 'user_to_system', 'received', serviceFee, '用户服务费返现', 'system']
+ );
+ await db.execute(
+ 'INSERT INTO distribution (user_id,agent_id, amount, type) VALUES (?, ?, ?,?)',
+ [userId, 3512, serviceFee, 'user']
+ )
+ }
+ await db.execute(
+ 'UPDATE users SET is_distribute = ? WHERE id = ?',
+ [true, userId]
+ );
+ // 提交事务
+ await db.query('COMMIT');
+
+ res.json({
+ success: true,
+ message: '服务费扣除成功',
+ data: {
+ user_id: userId,
+ username: user.username,
+ deducted_amount: serviceFee,
+ remaining_balance: currentBalance - serviceFee
+ }
+ });
+
+ } catch (error) {
+ try {
+ // 发生错误时回滚事务
+ await db.query('ROLLBACK');
+ } catch (rollbackError) {
+ console.error('回滚失败:', rollbackError);
+ }
+ console.error('扣除服务费失败:', error);
+ res.status(500).json({ success: false, message: '扣除服务费失败' });
+ }
+});
module.exports = router;
\ No newline at end of file
diff --git a/services/alipayservice.js b/services/alipayservice.js
index 6b82db0..977d58d 100644
--- a/services/alipayservice.js
+++ b/services/alipayservice.js
@@ -6,18 +6,66 @@ const fs = require('fs');
class AlipayService {
constructor() {
- // 读取密钥文件
- const privateKeyPath = path.join(__dirname, '../certs/alipay-private-key.pem');
- const publicKeyPath = path.join(__dirname, '../certs/alipay-public-key.pem');
+ this.privateKey = null;
+ this.alipayPublicKey = null;
+ this.alipaySdk = null;
+ this.isInitialized = false;
- const privateKey = fs.readFileSync(privateKeyPath, 'utf8');
- const alipayPublicKey = fs.readFileSync(publicKeyPath, 'utf8');
+ this.initializeAlipay();
+ }
+
+ /**
+ * 初始化支付宝服务
+ */
+ initializeAlipay() {
+ try {
+ // 读取密钥文件
+ const privateKeyPath = this.resolveCertPath('../certs/alipay-private-key.pem');
+ const publicKeyPath = this.resolveCertPath('../certs/alipay-public-key.pem');
+
+ console.log('支付宝私钥路径:', privateKeyPath);
+ console.log('支付宝公钥路径:', publicKeyPath);
+
+ // 验证文件有效性
+ if (!this.isValidFile(privateKeyPath)) {
+ throw new Error(`支付宝私钥文件无效或不存在: ${privateKeyPath}`);
+ }
+
+ if (!this.isValidFile(publicKeyPath)) {
+ throw new Error(`支付宝公钥文件无效或不存在: ${publicKeyPath}`);
+ }
+
+ console.log('尝试加载支付宝私钥文件:', privateKeyPath);
+ this.privateKey = fs.readFileSync(privateKeyPath, 'utf8');
+ console.log('支付宝私钥加载成功');
+
+ console.log('尝试加载支付宝公钥文件:', publicKeyPath);
+ this.alipayPublicKey = fs.readFileSync(publicKeyPath, 'utf8');
+ console.log('支付宝公钥加载成功');
+
+ this.initializeSDK();
+
+ } catch (error) {
+ console.error('支付宝服务初始化失败:', error.message);
+ console.error('支付宝功能将不可用');
+ // 不抛出错误,允许服务继续运行
+ }
+ }
+
+ /**
+ * 初始化支付宝SDK
+ */
+ initializeSDK() {
+ if (!this.privateKey || !this.alipayPublicKey) {
+ console.warn('支付宝密钥未加载,跳过SDK初始化');
+ return;
+ }
// 支付宝配置
this.config = {
appId: process.env.ALIPAY_APP_ID || '2021001161683774', // 替换为实际的应用ID
- privateKey: privateKey, // 从文件读取的应用私钥
- alipayPublicKey: alipayPublicKey, // 从文件读取的支付宝公钥
+ privateKey: this.privateKey, // 从文件读取的应用私钥
+ alipayPublicKey: this.alipayPublicKey, // 从文件读取的支付宝公钥
gateway: 'https://openapi.alipay.com/gateway.do', // 支付宝网关地址
signType: 'RSA2',
charset: 'utf-8',
@@ -34,6 +82,40 @@ class AlipayService {
signType: this.config.signType,
timeout: this.config.timeout
});
+
+ this.isInitialized = true;
+ console.log('支付宝SDK初始化成功');
+ }
+
+ /**
+ * 解析证书文件路径
+ * @param {string} relativePath - 相对路径
+ * @returns {string} 绝对路径
+ */
+ resolveCertPath(relativePath) {
+ return path.resolve(__dirname, relativePath);
+ }
+
+ /**
+ * 验证文件是否有效
+ * @param {string} filePath - 文件路径
+ * @returns {boolean} 是否为有效文件
+ */
+ isValidFile(filePath) {
+ try {
+ const stats = fs.statSync(filePath);
+ return stats.isFile();
+ } catch (error) {
+ return false;
+ }
+ }
+
+ /**
+ * 检查支付宝服务是否已初始化
+ * @returns {boolean} 是否已初始化
+ */
+ isServiceAvailable() {
+ return this.isInitialized && this.alipaySdk !== null;
}
/**
@@ -46,6 +128,11 @@ class AlipayService {
* @returns {Promise