更新服务器信息为新的 IP 地址,调整相关文档和代码中的默认配置,确保部署和连接的一致性。同时,优化订单管理界面,增强商品信息的格式化逻辑,提升用户体验。
This commit is contained in:
168
scripts/auto-unbind-expired-simple.js
Normal file
168
scripts/auto-unbind-expired-simple.js
Normal file
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 自动解绑定时任务 - 简化版(直接连接MySQL)
|
||||
*
|
||||
* 功能:定时检查并解绑过期的推荐关系
|
||||
*
|
||||
* 解绑条件:
|
||||
* 1. 绑定状态为 active
|
||||
* 2. 过期时间已到(expiry_date < NOW)
|
||||
* 3. 期间没有任何购买(purchase_count = 0)
|
||||
*
|
||||
* 使用方式:
|
||||
* 1. 确保已安装 mysql2: npm install mysql2
|
||||
* 2. 配置环境变量或修改下方 DB_CONFIG
|
||||
* 3. 手动执行:node scripts/auto-unbind-expired-simple.js
|
||||
* 4. 宝塔定时任务:每天 02:00 执行
|
||||
*/
|
||||
|
||||
const mysql = require('mysql2/promise')
|
||||
require('dotenv').config()
|
||||
|
||||
// 数据库配置(从环境变量读取)
|
||||
const DB_CONFIG = {
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: parseInt(process.env.DB_PORT) || 3306,
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
database: process.env.DB_NAME || 'mycontent_db',
|
||||
charset: 'utf8mb4'
|
||||
}
|
||||
|
||||
async function autoUnbind() {
|
||||
console.log('=' .repeat(60))
|
||||
console.log('自动解绑定时任务')
|
||||
console.log('执行时间:', new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }))
|
||||
console.log('=' .repeat(60))
|
||||
console.log()
|
||||
|
||||
let connection
|
||||
|
||||
try {
|
||||
// 连接数据库
|
||||
connection = await mysql.createConnection(DB_CONFIG)
|
||||
console.log(`✅ 已连接到数据库: ${DB_CONFIG.database}`)
|
||||
console.log()
|
||||
|
||||
// 1. 查询需要解绑的记录
|
||||
console.log('步骤 1: 查询需要解绑的记录...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
const [expiredBindings] = await connection.execute(`
|
||||
SELECT
|
||||
id,
|
||||
referee_id,
|
||||
referrer_id,
|
||||
binding_date,
|
||||
expiry_date,
|
||||
purchase_count,
|
||||
total_commission
|
||||
FROM referral_bindings
|
||||
WHERE status = 'active'
|
||||
AND expiry_date < NOW()
|
||||
AND purchase_count = 0
|
||||
ORDER BY expiry_date ASC
|
||||
`)
|
||||
|
||||
if (expiredBindings.length === 0) {
|
||||
console.log('✅ 无需解绑的记录')
|
||||
console.log()
|
||||
console.log('=' .repeat(60))
|
||||
console.log('任务完成')
|
||||
console.log('=' .repeat(60))
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`找到 ${expiredBindings.length} 条需要解绑的记录`)
|
||||
console.log()
|
||||
|
||||
// 2. 输出明细
|
||||
console.log('步骤 2: 解绑明细')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
expiredBindings.forEach((binding, index) => {
|
||||
const bindingDate = new Date(binding.binding_date).toLocaleDateString('zh-CN')
|
||||
const expiryDate = new Date(binding.expiry_date).toLocaleDateString('zh-CN')
|
||||
const daysExpired = Math.floor((Date.now() - new Date(binding.expiry_date).getTime()) / (1000 * 60 * 60 * 24))
|
||||
|
||||
console.log(`${index + 1}. 用户 ${binding.referee_id}`)
|
||||
console.log(` 推荐人: ${binding.referrer_id}`)
|
||||
console.log(` 绑定时间: ${bindingDate}`)
|
||||
console.log(` 过期时间: ${expiryDate} (已过期 ${daysExpired} 天)`)
|
||||
console.log(` 购买次数: ${binding.purchase_count}`)
|
||||
console.log(` 累计佣金: ¥${(binding.total_commission || 0).toFixed(2)}`)
|
||||
console.log()
|
||||
})
|
||||
|
||||
// 3. 批量更新为 expired
|
||||
console.log('步骤 3: 执行解绑操作...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
const ids = expiredBindings.map(b => b.id)
|
||||
const placeholders = ids.map(() => '?').join(',')
|
||||
|
||||
const [result] = await connection.execute(
|
||||
`UPDATE referral_bindings SET status = 'expired' WHERE id IN (${placeholders})`,
|
||||
ids
|
||||
)
|
||||
|
||||
console.log(`✅ 已成功解绑 ${result.affectedRows} 条记录`)
|
||||
console.log()
|
||||
|
||||
// 4. 更新推荐人的推广数量
|
||||
console.log('步骤 4: 更新推荐人统计...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
const referrerCounts = {}
|
||||
expiredBindings.forEach(binding => {
|
||||
referrerCounts[binding.referrer_id] = (referrerCounts[binding.referrer_id] || 0) + 1
|
||||
})
|
||||
|
||||
for (const [referrerId, count] of Object.entries(referrerCounts)) {
|
||||
await connection.execute(
|
||||
`UPDATE users SET referral_count = GREATEST(referral_count - ?, 0) WHERE id = ?`,
|
||||
[count, referrerId]
|
||||
)
|
||||
console.log(` - ${referrerId}: -${count} 个绑定`)
|
||||
}
|
||||
|
||||
console.log(`✅ 已更新 ${Object.keys(referrerCounts).length} 个推荐人的统计数据`)
|
||||
console.log()
|
||||
|
||||
// 5. 总结
|
||||
console.log('=' .repeat(60))
|
||||
console.log('✅ 任务完成')
|
||||
console.log(` - 解绑记录数: ${expiredBindings.length}`)
|
||||
console.log(` - 受影响推荐人: ${Object.keys(referrerCounts).length}`)
|
||||
console.log('=' .repeat(60))
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 任务执行失败:', error.message)
|
||||
console.error(error.stack)
|
||||
throw error
|
||||
} finally {
|
||||
// 关闭数据库连接
|
||||
if (connection) {
|
||||
await connection.end()
|
||||
console.log('\n数据库连接已关闭')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
try {
|
||||
await autoUnbind()
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error('\n❌ 脚本执行失败')
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 运行
|
||||
if (require.main === module) {
|
||||
main()
|
||||
}
|
||||
|
||||
module.exports = { autoUnbind }
|
||||
170
scripts/auto-unbind-expired.js
Normal file
170
scripts/auto-unbind-expired.js
Normal file
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 自动解绑定时任务
|
||||
*
|
||||
* 功能:定时检查并解绑过期的推荐关系
|
||||
*
|
||||
* 解绑条件:
|
||||
* 1. 绑定状态为 active
|
||||
* 2. 过期时间已到(expiry_date < NOW)
|
||||
* 3. 期间没有任何购买(purchase_count = 0)
|
||||
*
|
||||
* 执行方式:
|
||||
* - 手动执行:node scripts/auto-unbind-expired.js
|
||||
* - 定时任务:配置 cron 每天凌晨2点执行
|
||||
*
|
||||
* 宝塔面板配置:
|
||||
* 计划任务 -> Shell脚本
|
||||
* 执行周期:每天 02:00
|
||||
* 脚本内容:cd /www/wwwroot/soul && node scripts/auto-unbind-expired.js
|
||||
*/
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
// 动态加载数据库模块
|
||||
async function loadDB() {
|
||||
const dbPath = path.join(__dirname, '../lib/db.ts')
|
||||
|
||||
// 如果是 TypeScript 文件,需要使用 ts-node 或编译后的版本
|
||||
if (fs.existsSync(dbPath)) {
|
||||
// 尝试导入编译后的 JS 文件
|
||||
const compiledPath = path.join(__dirname, '../.next/server/lib/db.js')
|
||||
if (fs.existsSync(compiledPath)) {
|
||||
return require(compiledPath)
|
||||
}
|
||||
|
||||
// 如果没有编译版本,尝试使用 ts-node
|
||||
try {
|
||||
require('ts-node/register')
|
||||
return require(dbPath)
|
||||
} catch (e) {
|
||||
console.error('❌ 无法加载数据库模块,请确保已编译或安装 ts-node')
|
||||
process.exit(1)
|
||||
}
|
||||
} else {
|
||||
console.error('❌ 找不到数据库模块文件')
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
async function autoUnbind() {
|
||||
console.log('=' .repeat(60))
|
||||
console.log('自动解绑定时任务')
|
||||
console.log('执行时间:', new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }))
|
||||
console.log('=' .repeat(60))
|
||||
console.log()
|
||||
|
||||
try {
|
||||
// 加载数据库模块
|
||||
const { query } = await loadDB()
|
||||
|
||||
// 1. 查询需要解绑的记录
|
||||
console.log('步骤 1: 查询需要解绑的记录...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
const expiredBindings = await query(`
|
||||
SELECT
|
||||
id,
|
||||
referee_id,
|
||||
referrer_id,
|
||||
binding_date,
|
||||
expiry_date,
|
||||
purchase_count,
|
||||
total_commission
|
||||
FROM referral_bindings
|
||||
WHERE status = 'active'
|
||||
AND expiry_date < NOW()
|
||||
AND purchase_count = 0
|
||||
ORDER BY expiry_date ASC
|
||||
`)
|
||||
|
||||
if (expiredBindings.length === 0) {
|
||||
console.log('✅ 无需解绑的记录')
|
||||
console.log()
|
||||
console.log('=' .repeat(60))
|
||||
console.log('任务完成')
|
||||
console.log('=' .repeat(60))
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`找到 ${expiredBindings.length} 条需要解绑的记录`)
|
||||
console.log()
|
||||
|
||||
// 2. 输出明细
|
||||
console.log('步骤 2: 解绑明细')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
expiredBindings.forEach((binding, index) => {
|
||||
const bindingDate = new Date(binding.binding_date).toLocaleDateString('zh-CN')
|
||||
const expiryDate = new Date(binding.expiry_date).toLocaleDateString('zh-CN')
|
||||
const daysExpired = Math.floor((Date.now() - new Date(binding.expiry_date).getTime()) / (1000 * 60 * 60 * 24))
|
||||
|
||||
console.log(`${index + 1}. 用户 ${binding.referee_id}`)
|
||||
console.log(` 推荐人: ${binding.referrer_id}`)
|
||||
console.log(` 绑定时间: ${bindingDate}`)
|
||||
console.log(` 过期时间: ${expiryDate} (已过期 ${daysExpired} 天)`)
|
||||
console.log(` 购买次数: ${binding.purchase_count}`)
|
||||
console.log(` 累计佣金: ¥${(binding.total_commission || 0).toFixed(2)}`)
|
||||
console.log()
|
||||
})
|
||||
|
||||
// 3. 批量更新为 expired
|
||||
console.log('步骤 3: 执行解绑操作...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
const ids = expiredBindings.map(b => b.id)
|
||||
const result = await query(`
|
||||
UPDATE referral_bindings
|
||||
SET status = 'expired'
|
||||
WHERE id IN (${ids.map(() => '?').join(',')})
|
||||
`, ids)
|
||||
|
||||
console.log(`✅ 已成功解绑 ${result.affectedRows || expiredBindings.length} 条记录`)
|
||||
console.log()
|
||||
|
||||
// 4. 更新推荐人的推广数量
|
||||
console.log('步骤 4: 更新推荐人统计...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
const referrerIds = [...new Set(expiredBindings.map(b => b.referrer_id))]
|
||||
|
||||
for (const referrerId of referrerIds) {
|
||||
const count = expiredBindings.filter(b => b.referrer_id === referrerId).length
|
||||
await query(`
|
||||
UPDATE users
|
||||
SET referral_count = GREATEST(referral_count - ?, 0)
|
||||
WHERE id = ?
|
||||
`, [count, referrerId])
|
||||
|
||||
console.log(` - ${referrerId}: -${count} 个绑定`)
|
||||
}
|
||||
|
||||
console.log(`✅ 已更新 ${referrerIds.length} 个推荐人的统计数据`)
|
||||
console.log()
|
||||
|
||||
// 5. 总结
|
||||
console.log('=' .repeat(60))
|
||||
console.log('✅ 任务完成')
|
||||
console.log(` - 解绑记录数: ${expiredBindings.length}`)
|
||||
console.log(` - 受影响推荐人: ${referrerIds.length}`)
|
||||
console.log('=' .repeat(60))
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 任务执行失败:', error)
|
||||
console.error(error.stack)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
autoUnbind().then(() => {
|
||||
process.exit(0)
|
||||
}).catch((err) => {
|
||||
console.error('❌ 脚本执行异常:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { autoUnbind }
|
||||
62
scripts/check_deployment.py
Normal file
62
scripts/check_deployment.py
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""检查部署状态"""
|
||||
|
||||
import paramiko
|
||||
import sys
|
||||
|
||||
SSH_CONFIG = {
|
||||
'hostname': '43.139.27.93',
|
||||
'port': 22022,
|
||||
'username': 'root',
|
||||
'password': 'Zhiqun1984'
|
||||
}
|
||||
|
||||
def run_command(ssh, cmd, description):
|
||||
"""执行SSH命令"""
|
||||
print(f"\n[CMD] {description}")
|
||||
print(f" > {cmd}")
|
||||
stdin, stdout, stderr = ssh.exec_command(cmd)
|
||||
output = stdout.read().decode('utf-8', errors='ignore')
|
||||
error = stderr.read().decode('utf-8', errors='ignore')
|
||||
|
||||
if output:
|
||||
print(output)
|
||||
if error and 'warning' not in error.lower():
|
||||
print(f"[ERROR] {error}")
|
||||
|
||||
return output
|
||||
|
||||
def main():
|
||||
print("="*60)
|
||||
print("Deployment Status Check")
|
||||
print("="*60)
|
||||
|
||||
try:
|
||||
# SSH连接
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect(**SSH_CONFIG)
|
||||
print("[OK] SSH connected")
|
||||
|
||||
# 1. 检查PM2状态
|
||||
run_command(ssh, 'pm2 status', '1. PM2 process status')
|
||||
|
||||
# 2. 检查最新日志
|
||||
run_command(ssh, 'pm2 logs soul --lines 20 --nostream', '2. Recent PM2 logs')
|
||||
|
||||
# 3. 检查端口监听
|
||||
run_command(ssh, 'netstat -tuln | grep 30006', '3. Port 30006 listening status')
|
||||
|
||||
# 4. 验证API是否正常
|
||||
run_command(ssh, 'curl -s http://localhost:30006/api/config | head -c 200', '4. API health check')
|
||||
|
||||
ssh.close()
|
||||
print("\n[OK] Check completed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -45,12 +45,12 @@ DEFAULT_SSH_PORT = int(os.environ.get("DEPLOY_SSH_PORT", "22022"))
|
||||
def get_cfg():
|
||||
"""获取基础部署配置(deploy 模式与 devlop 共用 SSH/宝塔)"""
|
||||
return {
|
||||
"host": os.environ.get("DEPLOY_HOST", "42.194.232.22"),
|
||||
"host": os.environ.get("DEPLOY_HOST", "43.139.27.93"),
|
||||
"user": os.environ.get("DEPLOY_USER", "root"),
|
||||
"password": os.environ.get("DEPLOY_PASSWORD", "Zhiqun1984"),
|
||||
"ssh_key": os.environ.get("DEPLOY_SSH_KEY", ""),
|
||||
"project_path": os.environ.get("DEPLOY_PROJECT_PATH", DEPLOY_PROJECT_PATH),
|
||||
"panel_url": os.environ.get("BAOTA_PANEL_URL", "https://42.194.232.22:9988"),
|
||||
"panel_url": os.environ.get("BAOTA_PANEL_URL", "https://43.139.27.93:9988"),
|
||||
"api_key": os.environ.get("BAOTA_API_KEY", "hsAWqFSi0GOCrunhmYdkxy92tBXfqYjd"),
|
||||
"pm2_name": os.environ.get("DEPLOY_PM2_APP", DEPLOY_PM2_APP),
|
||||
"site_url": os.environ.get("DEPLOY_SITE_URL", DEPLOY_SITE_URL),
|
||||
|
||||
162
scripts/migrate_binding_fields.py
Normal file
162
scripts/migrate_binding_fields.py
Normal file
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
数据库迁移脚本:为 referral_bindings 表添加新字段
|
||||
用于支持新的分销逻辑(立即切换绑定、购买累加)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pymysql
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
# 数据库配置
|
||||
DB_CONFIG = {
|
||||
'host': os.getenv('DB_HOST', 'localhost'),
|
||||
'port': int(os.getenv('DB_PORT', 3306)),
|
||||
'user': os.getenv('DB_USER', 'root'),
|
||||
'password': os.getenv('DB_PASSWORD', ''),
|
||||
'database': os.getenv('DB_NAME', 'mycontent_db'),
|
||||
'charset': 'utf8mb4'
|
||||
}
|
||||
|
||||
def execute_sql(cursor, sql, description):
|
||||
"""执行SQL并打印结果"""
|
||||
try:
|
||||
cursor.execute(sql)
|
||||
print(f"✅ {description}")
|
||||
return True
|
||||
except pymysql.err.OperationalError as e:
|
||||
if 'Duplicate' in str(e) or 'already exists' in str(e):
|
||||
print(f"⚠️ {description} (已存在,跳过)")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ {description} 失败: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ {description} 失败: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("数据库迁移:referral_bindings 表字段升级")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# 连接数据库
|
||||
try:
|
||||
conn = pymysql.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
print(f"✅ 已连接到数据库: {DB_CONFIG['database']}")
|
||||
print()
|
||||
except Exception as e:
|
||||
print(f"❌ 数据库连接失败: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# 1. 添加新字段
|
||||
print("步骤 1: 添加新字段")
|
||||
print("-" * 60)
|
||||
|
||||
execute_sql(
|
||||
cursor,
|
||||
"""ALTER TABLE referral_bindings
|
||||
ADD COLUMN last_purchase_date DATETIME NULL COMMENT '最后一次购买时间'""",
|
||||
"添加字段 last_purchase_date"
|
||||
)
|
||||
|
||||
execute_sql(
|
||||
cursor,
|
||||
"""ALTER TABLE referral_bindings
|
||||
ADD COLUMN purchase_count INT DEFAULT 0 COMMENT '购买次数'""",
|
||||
"添加字段 purchase_count"
|
||||
)
|
||||
|
||||
execute_sql(
|
||||
cursor,
|
||||
"""ALTER TABLE referral_bindings
|
||||
ADD COLUMN total_commission DECIMAL(10,2) DEFAULT 0.00 COMMENT '累计佣金'""",
|
||||
"添加字段 total_commission"
|
||||
)
|
||||
|
||||
print()
|
||||
|
||||
# 2. 添加索引
|
||||
print("步骤 2: 添加索引")
|
||||
print("-" * 60)
|
||||
|
||||
execute_sql(
|
||||
cursor,
|
||||
"""ALTER TABLE referral_bindings
|
||||
ADD INDEX idx_referee_status (referee_id, status)""",
|
||||
"添加索引 idx_referee_status"
|
||||
)
|
||||
|
||||
execute_sql(
|
||||
cursor,
|
||||
"""ALTER TABLE referral_bindings
|
||||
ADD INDEX idx_expiry_purchase (expiry_date, purchase_count, status)""",
|
||||
"添加索引 idx_expiry_purchase"
|
||||
)
|
||||
|
||||
print()
|
||||
|
||||
# 3. 修改 status 枚举
|
||||
print("步骤 3: 更新 status 枚举(添加 cancelled)")
|
||||
print("-" * 60)
|
||||
|
||||
execute_sql(
|
||||
cursor,
|
||||
"""ALTER TABLE referral_bindings
|
||||
MODIFY COLUMN status ENUM('active', 'converted', 'expired', 'cancelled')
|
||||
DEFAULT 'active' COMMENT '绑定状态'""",
|
||||
"更新 status 枚举类型"
|
||||
)
|
||||
|
||||
print()
|
||||
|
||||
# 提交更改
|
||||
conn.commit()
|
||||
|
||||
# 4. 验证字段
|
||||
print("步骤 4: 验证迁移结果")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("SHOW COLUMNS FROM referral_bindings")
|
||||
columns = cursor.fetchall()
|
||||
|
||||
required_fields = ['last_purchase_date', 'purchase_count', 'total_commission']
|
||||
found_fields = [col[0] for col in columns]
|
||||
|
||||
for field in required_fields:
|
||||
if field in found_fields:
|
||||
print(f"✅ 字段 {field} 已存在")
|
||||
else:
|
||||
print(f"❌ 字段 {field} 未找到")
|
||||
|
||||
print()
|
||||
|
||||
# 5. 显示索引
|
||||
print("步骤 5: 当前索引列表")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("SHOW INDEX FROM referral_bindings")
|
||||
indexes = cursor.fetchall()
|
||||
|
||||
for idx in indexes:
|
||||
print(f" - {idx[2]} ({idx[4]})")
|
||||
|
||||
print()
|
||||
|
||||
# 关闭连接
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
print("=" * 60)
|
||||
print("✅ 迁移完成!")
|
||||
print("=" * 60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
158
scripts/migrate_db_simple.py
Normal file
158
scripts/migrate_db_simple.py
Normal file
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""数据库迁移脚本 - 简化版"""
|
||||
|
||||
import sys
|
||||
import pymysql
|
||||
|
||||
# 数据库配置(从 lib/db.ts 获取)
|
||||
DB_CONFIG = {
|
||||
'host': '56b4c23f6853c.gz.cdb.myqcloud.com',
|
||||
'port': 14413,
|
||||
'user': 'cdb_outerroot',
|
||||
'password': 'Zhiqun1984',
|
||||
'database': 'soul_miniprogram',
|
||||
'charset': 'utf8mb4'
|
||||
}
|
||||
|
||||
def execute_sql(cursor, sql, description):
|
||||
"""执行SQL并打印结果"""
|
||||
try:
|
||||
cursor.execute(sql)
|
||||
print(f"[OK] {description}")
|
||||
return True
|
||||
except pymysql.err.OperationalError as e:
|
||||
if 'Duplicate' in str(e) or 'already exists' in str(e):
|
||||
print(f"[SKIP] {description} (already exists)")
|
||||
return True
|
||||
else:
|
||||
print(f"[ERROR] {description}: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"[ERROR] {description}: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
print("="*60)
|
||||
print("Database Migration: referral_bindings table upgrade")
|
||||
print("="*60)
|
||||
print()
|
||||
|
||||
try:
|
||||
# 连接数据库
|
||||
print("Connecting to database...")
|
||||
conn = pymysql.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
print(f"[OK] Connected to: {DB_CONFIG['database']}")
|
||||
print()
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Database connection failed: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# 1. 添加新字段
|
||||
print("Step 1: Adding new fields")
|
||||
print("-"*60)
|
||||
|
||||
execute_sql(
|
||||
cursor,
|
||||
"""ALTER TABLE referral_bindings
|
||||
ADD COLUMN last_purchase_date DATETIME NULL COMMENT 'Last purchase time'""",
|
||||
"Add field: last_purchase_date"
|
||||
)
|
||||
|
||||
execute_sql(
|
||||
cursor,
|
||||
"""ALTER TABLE referral_bindings
|
||||
ADD COLUMN purchase_count INT DEFAULT 0 COMMENT 'Purchase count'""",
|
||||
"Add field: purchase_count"
|
||||
)
|
||||
|
||||
execute_sql(
|
||||
cursor,
|
||||
"""ALTER TABLE referral_bindings
|
||||
ADD COLUMN total_commission DECIMAL(10,2) DEFAULT 0.00 COMMENT 'Total commission'""",
|
||||
"Add field: total_commission"
|
||||
)
|
||||
|
||||
print()
|
||||
|
||||
# 2. 添加索引
|
||||
print("Step 2: Adding indexes")
|
||||
print("-"*60)
|
||||
|
||||
execute_sql(
|
||||
cursor,
|
||||
"""ALTER TABLE referral_bindings
|
||||
ADD INDEX idx_referee_status (referee_id, status)""",
|
||||
"Add index: idx_referee_status"
|
||||
)
|
||||
|
||||
execute_sql(
|
||||
cursor,
|
||||
"""ALTER TABLE referral_bindings
|
||||
ADD INDEX idx_expiry_purchase (expiry_date, purchase_count, status)""",
|
||||
"Add index: idx_expiry_purchase"
|
||||
)
|
||||
|
||||
print()
|
||||
|
||||
# 3. 修改 status 枚举
|
||||
print("Step 3: Updating status enum")
|
||||
print("-"*60)
|
||||
|
||||
execute_sql(
|
||||
cursor,
|
||||
"""ALTER TABLE referral_bindings
|
||||
MODIFY COLUMN status ENUM('active', 'converted', 'expired', 'cancelled')
|
||||
DEFAULT 'active' COMMENT 'Binding status'""",
|
||||
"Update status enum type"
|
||||
)
|
||||
|
||||
print()
|
||||
|
||||
# 提交更改
|
||||
conn.commit()
|
||||
|
||||
# 4. 验证字段
|
||||
print("Step 4: Verifying migration")
|
||||
print("-"*60)
|
||||
|
||||
cursor.execute("SHOW COLUMNS FROM referral_bindings")
|
||||
columns = cursor.fetchall()
|
||||
|
||||
required_fields = ['last_purchase_date', 'purchase_count', 'total_commission']
|
||||
found_fields = [col[0] for col in columns]
|
||||
|
||||
for field in required_fields:
|
||||
if field in found_fields:
|
||||
print(f"[OK] Field {field} exists")
|
||||
else:
|
||||
print(f"[ERROR] Field {field} not found")
|
||||
|
||||
print()
|
||||
|
||||
# 5. 显示索引
|
||||
print("Step 5: Current indexes")
|
||||
print("-"*60)
|
||||
|
||||
cursor.execute("SHOW INDEX FROM referral_bindings")
|
||||
indexes = cursor.fetchall()
|
||||
|
||||
index_names = set()
|
||||
for idx in indexes:
|
||||
if idx[2] not in index_names:
|
||||
print(f" - {idx[2]} ({idx[4]})")
|
||||
index_names.add(idx[2])
|
||||
|
||||
print()
|
||||
|
||||
# 关闭连接
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
print("="*60)
|
||||
print("[OK] Migration completed!")
|
||||
print("="*60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
25
scripts/migration-add-binding-fields.sql
Normal file
25
scripts/migration-add-binding-fields.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
-- 新分销逻辑数据库迁移脚本
|
||||
-- 为 referral_bindings 表添加新字段
|
||||
|
||||
-- 1. 添加新字段
|
||||
ALTER TABLE referral_bindings
|
||||
ADD COLUMN IF NOT EXISTS last_purchase_date DATETIME NULL COMMENT '最后一次购买时间',
|
||||
ADD COLUMN IF NOT EXISTS purchase_count INT DEFAULT 0 COMMENT '购买次数',
|
||||
ADD COLUMN IF NOT EXISTS total_commission DECIMAL(10,2) DEFAULT 0.00 COMMENT '累计佣金';
|
||||
|
||||
-- 2. 添加索引优化查询
|
||||
ALTER TABLE referral_bindings
|
||||
ADD INDEX IF NOT EXISTS idx_referee_status (referee_id, status),
|
||||
ADD INDEX IF NOT EXISTS idx_expiry_purchase (expiry_date, purchase_count, status);
|
||||
|
||||
-- 3. 修改 status 枚举(添加 cancelled 状态)
|
||||
ALTER TABLE referral_bindings
|
||||
MODIFY COLUMN status ENUM('active', 'converted', 'expired', 'cancelled') DEFAULT 'active' COMMENT '绑定状态';
|
||||
|
||||
-- 4. 验证字段已添加
|
||||
SHOW COLUMNS FROM referral_bindings LIKE 'last_purchase_date';
|
||||
SHOW COLUMNS FROM referral_bindings LIKE 'purchase_count';
|
||||
SHOW COLUMNS FROM referral_bindings LIKE 'total_commission';
|
||||
|
||||
-- 5. 查看索引
|
||||
SHOW INDEX FROM referral_bindings;
|
||||
227
scripts/remove-referred-by-field-auto.py
Normal file
227
scripts/remove-referred-by-field-auto.py
Normal file
@@ -0,0 +1,227 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
删除 users.referred_by 冗余字段(自动执行版本)
|
||||
优化绑定关系存储,只使用 referral_bindings 表
|
||||
"""
|
||||
|
||||
import pymysql
|
||||
import sys
|
||||
|
||||
# 数据库配置(从 lib/db.ts 获取)
|
||||
DB_CONFIG = {
|
||||
'host': 'gz-cynosdbmysql-grp-kfcvxbby.sql.tencentcdb.com',
|
||||
'port': 27815,
|
||||
'user': 'root',
|
||||
'password': 'Aa112211',
|
||||
'database': 'soul_miniprogram',
|
||||
'charset': 'utf8mb4'
|
||||
}
|
||||
|
||||
def print_step(step, msg):
|
||||
"""打印步骤信息"""
|
||||
print('')
|
||||
print('=' * 70)
|
||||
print('步骤 {}: {}'.format(step, msg))
|
||||
print('=' * 70)
|
||||
|
||||
def execute_sql(cursor, sql, params=None):
|
||||
"""执行SQL并返回影响行数"""
|
||||
try:
|
||||
if params:
|
||||
cursor.execute(sql, params)
|
||||
else:
|
||||
cursor.execute(sql)
|
||||
return cursor.rowcount
|
||||
except Exception as e:
|
||||
print('执行失败: {}'.format(str(e)))
|
||||
raise
|
||||
|
||||
def main():
|
||||
connection = None
|
||||
|
||||
try:
|
||||
print('')
|
||||
print('=' * 70)
|
||||
print('删除 users.referred_by 冗余字段')
|
||||
print('=' * 70)
|
||||
|
||||
print_step(1, '连接数据库')
|
||||
connection = pymysql.connect(**DB_CONFIG)
|
||||
cursor = connection.cursor()
|
||||
print('已连接到数据库: {}'.format(DB_CONFIG['database']))
|
||||
|
||||
# ========================================
|
||||
# 步骤2: 备份当前 referred_by 数据
|
||||
# ========================================
|
||||
print_step(2, '备份 referred_by 数据(用于验证)')
|
||||
|
||||
cursor.execute('''
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
COUNT(referred_by) as has_referrer,
|
||||
COUNT(DISTINCT referred_by) as unique_referrers
|
||||
FROM users
|
||||
''')
|
||||
stats = cursor.fetchone()
|
||||
print('当前用户表统计:')
|
||||
print(' 总用户数: {}'.format(stats[0]))
|
||||
print(' 有推荐人的用户: {}'.format(stats[1]))
|
||||
print(' 唯一推荐人数: {}'.format(stats[2]))
|
||||
|
||||
# 导出 referred_by 数据到临时表(备份)
|
||||
cursor.execute('DROP TABLE IF EXISTS users_referred_by_backup')
|
||||
cursor.execute('''
|
||||
CREATE TABLE users_referred_by_backup AS
|
||||
SELECT id, referred_by, created_at
|
||||
FROM users
|
||||
WHERE referred_by IS NOT NULL
|
||||
''')
|
||||
connection.commit()
|
||||
|
||||
cursor.execute('SELECT COUNT(*) FROM users_referred_by_backup')
|
||||
backup_count = cursor.fetchone()[0]
|
||||
print('已备份 {} 条记录到 users_referred_by_backup 表'.format(backup_count))
|
||||
|
||||
# ========================================
|
||||
# 步骤3: 验证 referral_bindings 数据完整性
|
||||
# ========================================
|
||||
print_step(3, '验证 referral_bindings 数据完整性')
|
||||
|
||||
cursor.execute('''
|
||||
SELECT
|
||||
COUNT(*) as total_bindings,
|
||||
COUNT(DISTINCT referee_id) as unique_referees,
|
||||
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active_bindings
|
||||
FROM referral_bindings
|
||||
''')
|
||||
binding_stats = cursor.fetchone()
|
||||
print('推荐绑定表统计:')
|
||||
print(' 总绑定记录: {}'.format(binding_stats[0]))
|
||||
print(' 唯一被推荐人: {}'.format(binding_stats[1]))
|
||||
print(' 当前活跃绑定: {}'.format(binding_stats[2]))
|
||||
|
||||
# 检查数据一致性
|
||||
cursor.execute('''
|
||||
SELECT COUNT(*) FROM users u
|
||||
WHERE u.referred_by IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM referral_bindings rb
|
||||
WHERE rb.referee_id = u.id
|
||||
)
|
||||
''')
|
||||
inconsistent = cursor.fetchone()[0]
|
||||
if inconsistent > 0:
|
||||
print('警告: 发现 {} 个用户在 users.referred_by 有值但 referral_bindings 中无记录'.format(inconsistent))
|
||||
print('这些记录已备份到 users_referred_by_backup 表')
|
||||
else:
|
||||
print('数据一致性检查通过!')
|
||||
|
||||
# ========================================
|
||||
# 步骤4: 删除 referred_by 相关索引
|
||||
# ========================================
|
||||
print_step(4, '删除 referred_by 索引')
|
||||
|
||||
# 检查索引是否存在
|
||||
cursor.execute('''
|
||||
SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = %s
|
||||
AND table_name = 'users'
|
||||
AND index_name = 'idx_referred_by'
|
||||
''', (DB_CONFIG['database'],))
|
||||
|
||||
index_exists = cursor.fetchone()[0] > 0
|
||||
|
||||
if index_exists:
|
||||
cursor.execute('ALTER TABLE users DROP INDEX idx_referred_by')
|
||||
print('已删除索引: idx_referred_by')
|
||||
else:
|
||||
print('索引 idx_referred_by 不存在,跳过')
|
||||
|
||||
# ========================================
|
||||
# 步骤5: 删除 referred_by 字段
|
||||
# ========================================
|
||||
print_step(5, '删除 users.referred_by 字段')
|
||||
|
||||
# 检查字段是否存在
|
||||
cursor.execute('''
|
||||
SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = %s
|
||||
AND table_name = 'users'
|
||||
AND column_name = 'referred_by'
|
||||
''', (DB_CONFIG['database'],))
|
||||
|
||||
field_exists = cursor.fetchone()[0] > 0
|
||||
|
||||
if field_exists:
|
||||
cursor.execute('ALTER TABLE users DROP COLUMN referred_by')
|
||||
print('已删除字段: users.referred_by')
|
||||
else:
|
||||
print('字段 referred_by 不存在,跳过')
|
||||
|
||||
# ========================================
|
||||
# 步骤6: 提交更改
|
||||
# ========================================
|
||||
print_step(6, '提交数据库更改')
|
||||
connection.commit()
|
||||
print('所有更改已提交!')
|
||||
|
||||
# ========================================
|
||||
# 步骤7: 验证删除结果
|
||||
# ========================================
|
||||
print_step(7, '验证删除结果')
|
||||
|
||||
cursor.execute('''
|
||||
SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = %s
|
||||
AND table_name = 'users'
|
||||
AND column_name = 'referred_by'
|
||||
''', (DB_CONFIG['database'],))
|
||||
|
||||
still_exists = cursor.fetchone()[0] > 0
|
||||
|
||||
if still_exists:
|
||||
print('警告: 字段仍然存在!')
|
||||
else:
|
||||
print('验证通过: referred_by 字段已成功删除')
|
||||
|
||||
# 检查备份表
|
||||
cursor.execute('SELECT COUNT(*) FROM users_referred_by_backup')
|
||||
backup_rows = cursor.fetchone()[0]
|
||||
print('备份表保留了 {} 条记录(可选择稍后删除)'.format(backup_rows))
|
||||
|
||||
# ========================================
|
||||
# 完成
|
||||
# ========================================
|
||||
print('')
|
||||
print('=' * 70)
|
||||
print('优化完成!')
|
||||
print('=' * 70)
|
||||
print('')
|
||||
print('后续步骤:')
|
||||
print('1. 修改代码中所有使用 referred_by 的地方(已完成)')
|
||||
print('2. 部署新代码到服务器')
|
||||
print('3. 测试绑定和佣金功能')
|
||||
print('4. 确认无误后,可删除备份表: DROP TABLE users_referred_by_backup')
|
||||
print('')
|
||||
print('备份表可保留一段时间,确保数据安全。')
|
||||
|
||||
except Exception as e:
|
||||
print('')
|
||||
print('错误: {}'.format(str(e).encode('utf-8', errors='replace').decode('utf-8', errors='replace')))
|
||||
if connection:
|
||||
connection.rollback()
|
||||
print('已回滚所有更改')
|
||||
sys.exit(1)
|
||||
|
||||
finally:
|
||||
if connection:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
print('')
|
||||
print('数据库连接已关闭')
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('')
|
||||
print('即将自动执行删除 users.referred_by 字段...')
|
||||
print('')
|
||||
main()
|
||||
231
scripts/remove-referred-by-field.py
Normal file
231
scripts/remove-referred-by-field.py
Normal file
@@ -0,0 +1,231 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
删除 users.referred_by 冗余字段
|
||||
优化绑定关系存储,只使用 referral_bindings 表
|
||||
"""
|
||||
|
||||
import pymysql
|
||||
import sys
|
||||
|
||||
# 数据库配置(从 lib/db.ts 获取)
|
||||
DB_CONFIG = {
|
||||
'host': 'gz-cynosdbmysql-grp-kfcvxbby.sql.tencentcdb.com',
|
||||
'port': 27815,
|
||||
'user': 'root',
|
||||
'password': 'Aa112211',
|
||||
'database': 'soul_miniprogram',
|
||||
'charset': 'utf8mb4'
|
||||
}
|
||||
|
||||
def print_step(step, msg):
|
||||
"""打印步骤信息"""
|
||||
print('\n' + '=' * 70)
|
||||
print('步骤 {}: {}'.format(step, msg))
|
||||
print('=' * 70)
|
||||
|
||||
def execute_sql(cursor, sql, params=None):
|
||||
"""执行SQL并返回影响行数"""
|
||||
try:
|
||||
if params:
|
||||
cursor.execute(sql, params)
|
||||
else:
|
||||
cursor.execute(sql)
|
||||
return cursor.rowcount
|
||||
except Exception as e:
|
||||
print('执行失败: {}'.format(str(e)))
|
||||
raise
|
||||
|
||||
def main():
|
||||
connection = None
|
||||
|
||||
try:
|
||||
print_step(1, '连接数据库')
|
||||
connection = pymysql.connect(**DB_CONFIG)
|
||||
cursor = connection.cursor()
|
||||
print('已连接到数据库: {}'.format(DB_CONFIG['database']))
|
||||
|
||||
# ========================================
|
||||
# 步骤2: 备份当前 referred_by 数据
|
||||
# ========================================
|
||||
print_step(2, '备份 referred_by 数据(用于验证)')
|
||||
|
||||
cursor.execute('''
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
COUNT(referred_by) as has_referrer,
|
||||
COUNT(DISTINCT referred_by) as unique_referrers
|
||||
FROM users
|
||||
''')
|
||||
stats = cursor.fetchone()
|
||||
print('当前用户表统计:')
|
||||
print(' 总用户数: {}'.format(stats[0]))
|
||||
print(' 有推荐人的用户: {}'.format(stats[1]))
|
||||
print(' 唯一推荐人数: {}'.format(stats[2]))
|
||||
|
||||
# 导出 referred_by 数据到临时表(备份)
|
||||
cursor.execute('DROP TABLE IF EXISTS users_referred_by_backup')
|
||||
cursor.execute('''
|
||||
CREATE TABLE users_referred_by_backup AS
|
||||
SELECT id, referred_by, created_at
|
||||
FROM users
|
||||
WHERE referred_by IS NOT NULL
|
||||
''')
|
||||
backup_count = cursor.rowcount
|
||||
print('已备份 {} 条记录到 users_referred_by_backup 表'.format(backup_count))
|
||||
|
||||
# ========================================
|
||||
# 步骤3: 验证 referral_bindings 数据完整性
|
||||
# ========================================
|
||||
print_step(3, '验证 referral_bindings 数据完整性')
|
||||
|
||||
cursor.execute('''
|
||||
SELECT
|
||||
COUNT(*) as total_bindings,
|
||||
COUNT(DISTINCT referee_id) as unique_referees,
|
||||
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active_bindings
|
||||
FROM referral_bindings
|
||||
''')
|
||||
binding_stats = cursor.fetchone()
|
||||
print('推荐绑定表统计:')
|
||||
print(' 总绑定记录: {}'.format(binding_stats[0]))
|
||||
print(' 唯一被推荐人: {}'.format(binding_stats[1]))
|
||||
print(' 当前活跃绑定: {}'.format(binding_stats[2]))
|
||||
|
||||
# 检查数据一致性
|
||||
cursor.execute('''
|
||||
SELECT COUNT(*) FROM users u
|
||||
WHERE u.referred_by IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM referral_bindings rb
|
||||
WHERE rb.referee_id = u.id
|
||||
)
|
||||
''')
|
||||
inconsistent = cursor.fetchone()[0]
|
||||
if inconsistent > 0:
|
||||
print('警告: 发现 {} 个用户在 users.referred_by 有值但 referral_bindings 中无记录'.format(inconsistent))
|
||||
print('这些记录可能是旧数据,删除字段后将丢失')
|
||||
else:
|
||||
print('数据一致性检查通过!')
|
||||
|
||||
# ========================================
|
||||
# 步骤4: 删除 referred_by 相关索引
|
||||
# ========================================
|
||||
print_step(4, '删除 referred_by 索引')
|
||||
|
||||
# 检查索引是否存在
|
||||
cursor.execute('''
|
||||
SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = %s
|
||||
AND table_name = 'users'
|
||||
AND index_name = 'idx_referred_by'
|
||||
''', (DB_CONFIG['database'],))
|
||||
|
||||
index_exists = cursor.fetchone()[0] > 0
|
||||
|
||||
if index_exists:
|
||||
cursor.execute('ALTER TABLE users DROP INDEX idx_referred_by')
|
||||
print('已删除索引: idx_referred_by')
|
||||
else:
|
||||
print('索引 idx_referred_by 不存在,跳过')
|
||||
|
||||
# ========================================
|
||||
# 步骤5: 删除 referred_by 字段
|
||||
# ========================================
|
||||
print_step(5, '删除 users.referred_by 字段')
|
||||
|
||||
# 检查字段是否存在
|
||||
cursor.execute('''
|
||||
SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = %s
|
||||
AND table_name = 'users'
|
||||
AND column_name = 'referred_by'
|
||||
''', (DB_CONFIG['database'],))
|
||||
|
||||
field_exists = cursor.fetchone()[0] > 0
|
||||
|
||||
if field_exists:
|
||||
cursor.execute('ALTER TABLE users DROP COLUMN referred_by')
|
||||
print('已删除字段: users.referred_by')
|
||||
else:
|
||||
print('字段 referred_by 不存在,跳过')
|
||||
|
||||
# ========================================
|
||||
# 步骤6: 提交更改
|
||||
# ========================================
|
||||
print_step(6, '提交数据库更改')
|
||||
connection.commit()
|
||||
print('所有更改已提交!')
|
||||
|
||||
# ========================================
|
||||
# 步骤7: 验证删除结果
|
||||
# ========================================
|
||||
print_step(7, '验证删除结果')
|
||||
|
||||
cursor.execute('''
|
||||
SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = %s
|
||||
AND table_name = 'users'
|
||||
AND column_name = 'referred_by'
|
||||
''', (DB_CONFIG['database'],))
|
||||
|
||||
still_exists = cursor.fetchone()[0] > 0
|
||||
|
||||
if still_exists:
|
||||
print('警告: 字段仍然存在!')
|
||||
else:
|
||||
print('验证通过: referred_by 字段已成功删除')
|
||||
|
||||
# 检查备份表
|
||||
cursor.execute('SELECT COUNT(*) FROM users_referred_by_backup')
|
||||
backup_rows = cursor.fetchone()[0]
|
||||
print('备份表保留了 {} 条记录(可选择稍后删除)'.format(backup_rows))
|
||||
|
||||
# ========================================
|
||||
# 完成
|
||||
# ========================================
|
||||
print('\n' + '=' * 70)
|
||||
print('优化完成!')
|
||||
print('=' * 70)
|
||||
print('\n后续步骤:')
|
||||
print('1. 修改代码中所有使用 referred_by 的地方')
|
||||
print('2. 部署新代码到服务器')
|
||||
print('3. 测试绑定和佣金功能')
|
||||
print('4. 确认无误后,可删除备份表: DROP TABLE users_referred_by_backup')
|
||||
print('\n备份表可保留一段时间,确保数据安全。')
|
||||
|
||||
except Exception as e:
|
||||
print('\n错误: {}'.format(str(e)))
|
||||
if connection:
|
||||
connection.rollback()
|
||||
print('已回滚所有更改')
|
||||
sys.exit(1)
|
||||
|
||||
finally:
|
||||
if connection:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
print('\n数据库连接已关闭')
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('\n' + '=' * 70)
|
||||
print('删除 users.referred_by 冗余字段')
|
||||
print('=' * 70)
|
||||
print('\n此脚本将执行以下操作:')
|
||||
print('1. 备份 referred_by 数据到 users_referred_by_backup 表')
|
||||
print('2. 删除 idx_referred_by 索引')
|
||||
print('3. 删除 users.referred_by 字段')
|
||||
print('\n警告: 此操作会修改数据库结构!')
|
||||
print('建议先在测试环境执行。')
|
||||
|
||||
try:
|
||||
raw_input_func = raw_input # Python 2
|
||||
except NameError:
|
||||
raw_input_func = input # Python 3
|
||||
|
||||
confirm = raw_input_func('\n确认执行?(输入 yes 继续): ')
|
||||
|
||||
if confirm.lower() != 'yes':
|
||||
print('已取消操作')
|
||||
sys.exit(0)
|
||||
|
||||
main()
|
||||
108
scripts/remove-referred-by-field.sql
Normal file
108
scripts/remove-referred-by-field.sql
Normal file
@@ -0,0 +1,108 @@
|
||||
-- ============================================================
|
||||
-- 删除 users.referred_by 冗余字段
|
||||
-- 优化绑定关系存储,只使用 referral_bindings 表
|
||||
-- ============================================================
|
||||
|
||||
-- 使用数据库
|
||||
USE soul_miniprogram;
|
||||
|
||||
-- ============================================================
|
||||
-- 步骤1: 备份 referred_by 数据
|
||||
-- ============================================================
|
||||
DROP TABLE IF EXISTS users_referred_by_backup;
|
||||
|
||||
CREATE TABLE users_referred_by_backup AS
|
||||
SELECT id, referred_by, created_at
|
||||
FROM users
|
||||
WHERE referred_by IS NOT NULL;
|
||||
|
||||
-- 查看备份统计
|
||||
SELECT
|
||||
COUNT(*) as '备份记录数',
|
||||
COUNT(DISTINCT referred_by) as '唯一推荐人数'
|
||||
FROM users_referred_by_backup;
|
||||
|
||||
-- ============================================================
|
||||
-- 步骤2: 验证 referral_bindings 数据完整性
|
||||
-- ============================================================
|
||||
|
||||
-- 检查绑定表统计
|
||||
SELECT
|
||||
COUNT(*) as '总绑定记录',
|
||||
COUNT(DISTINCT referee_id) as '唯一被推荐人',
|
||||
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as '当前活跃绑定'
|
||||
FROM referral_bindings;
|
||||
|
||||
-- 检查数据一致性(找出在users中有referred_by但在bindings中没有记录的用户)
|
||||
SELECT COUNT(*) as '不一致记录数' FROM users u
|
||||
WHERE u.referred_by IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM referral_bindings rb
|
||||
WHERE rb.referee_id = u.id
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- 步骤3: 删除 referred_by 索引
|
||||
-- ============================================================
|
||||
|
||||
-- 检查索引是否存在
|
||||
SELECT
|
||||
INDEX_NAME,
|
||||
COLUMN_NAME,
|
||||
SEQ_IN_INDEX
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema = 'soul_miniprogram'
|
||||
AND table_name = 'users'
|
||||
AND index_name = 'idx_referred_by';
|
||||
|
||||
-- 删除索引(如果存在)
|
||||
ALTER TABLE users DROP INDEX IF EXISTS idx_referred_by;
|
||||
|
||||
-- ============================================================
|
||||
-- 步骤4: 删除 referred_by 字段
|
||||
-- ============================================================
|
||||
|
||||
-- 检查字段是否存在
|
||||
SELECT
|
||||
COLUMN_NAME,
|
||||
COLUMN_TYPE,
|
||||
IS_NULLABLE
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'soul_miniprogram'
|
||||
AND table_name = 'users'
|
||||
AND column_name = 'referred_by';
|
||||
|
||||
-- 删除字段
|
||||
ALTER TABLE users DROP COLUMN referred_by;
|
||||
|
||||
-- ============================================================
|
||||
-- 步骤5: 验证删除结果
|
||||
-- ============================================================
|
||||
|
||||
-- 验证字段已删除
|
||||
SELECT COUNT(*) as '字段是否仍存在(应为0)'
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'soul_miniprogram'
|
||||
AND table_name = 'users'
|
||||
AND column_name = 'referred_by';
|
||||
|
||||
-- 验证备份表
|
||||
SELECT COUNT(*) as '备份记录数' FROM users_referred_by_backup;
|
||||
|
||||
-- ============================================================
|
||||
-- 完成!
|
||||
-- ============================================================
|
||||
|
||||
-- 查看users表当前结构
|
||||
SHOW COLUMNS FROM users;
|
||||
|
||||
-- 查看referral_bindings表当前结构
|
||||
SHOW COLUMNS FROM referral_bindings;
|
||||
|
||||
-- ============================================================
|
||||
-- 备注:
|
||||
-- 1. 备份表 users_referred_by_backup 可保留一段时间
|
||||
-- 2. 确认无误后,可执行: DROP TABLE users_referred_by_backup;
|
||||
-- 3. 代码已修改为只使用 referral_bindings 表
|
||||
-- 4. 部署后请测试绑定和佣金功能
|
||||
-- ============================================================
|
||||
145
scripts/test-referral-config.js
Normal file
145
scripts/test-referral-config.js
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 测试推广配置读取和佣金计算
|
||||
* 用于验证配置值是否正确
|
||||
*/
|
||||
|
||||
const mysql = require('mysql2/promise')
|
||||
|
||||
const DB_CONFIG = {
|
||||
host: 'gz-cynosdbmysql-grp-kfcvxbby.sql.tencentcdb.com',
|
||||
port: 27815,
|
||||
user: 'root',
|
||||
password: 'Aa112211',
|
||||
database: 'soul_miniprogram',
|
||||
charset: 'utf8mb4'
|
||||
}
|
||||
|
||||
async function testConfig() {
|
||||
let connection
|
||||
|
||||
try {
|
||||
console.log('=' .repeat(60))
|
||||
console.log('推广配置测试')
|
||||
console.log('=' .repeat(60))
|
||||
console.log()
|
||||
|
||||
// 1. 连接数据库
|
||||
console.log('步骤 1: 连接数据库...')
|
||||
connection = await mysql.createConnection(DB_CONFIG)
|
||||
console.log('✅ 已连接到数据库:', DB_CONFIG.database)
|
||||
console.log()
|
||||
|
||||
// 2. 读取配置
|
||||
console.log('步骤 2: 读取推广配置...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
const [configRows] = await connection.execute(
|
||||
`SELECT config_key, config_value FROM system_config WHERE config_key = 'referral_config'`
|
||||
)
|
||||
|
||||
if (configRows.length === 0) {
|
||||
console.log('⚠️ 数据库中没有 referral_config,使用默认值')
|
||||
console.log()
|
||||
console.log('默认配置:')
|
||||
console.log(' distributorShare: 90 (90%)')
|
||||
console.log(' minWithdrawAmount: 10')
|
||||
console.log(' bindingDays: 30')
|
||||
console.log(' userDiscount: 5')
|
||||
console.log()
|
||||
} else {
|
||||
const configValue = configRows[0].config_value
|
||||
let config
|
||||
|
||||
try {
|
||||
config = typeof configValue === 'string' ? JSON.parse(configValue) : configValue
|
||||
} catch (e) {
|
||||
console.error('❌ 配置解析失败:', e.message)
|
||||
return
|
||||
}
|
||||
|
||||
console.log('✅ 读取到的配置:')
|
||||
console.log(' distributorShare:', config.distributorShare, `(${config.distributorShare}%)`)
|
||||
console.log(' minWithdrawAmount:', config.minWithdrawAmount, '元')
|
||||
console.log(' bindingDays:', config.bindingDays, '天')
|
||||
console.log(' userDiscount:', config.userDiscount, `(${config.userDiscount}%)`)
|
||||
console.log(' enableAutoWithdraw:', config.enableAutoWithdraw)
|
||||
console.log()
|
||||
|
||||
// 3. 测试佣金计算
|
||||
console.log('步骤 3: 测试佣金计算...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
const distributorShareRate = config.distributorShare / 100
|
||||
console.log('分成比例(计算用):', distributorShareRate)
|
||||
console.log()
|
||||
|
||||
// 测试用例
|
||||
const testCases = [
|
||||
{ amount: 1.00, desc: '购买1元商品' },
|
||||
{ amount: 0.95, desc: '购买1元商品(5%折扣后)' },
|
||||
{ amount: 9.90, desc: '购买全书9.9元' }
|
||||
]
|
||||
|
||||
console.log('佣金计算结果:')
|
||||
testCases.forEach(test => {
|
||||
const commission = Math.round(test.amount * distributorShareRate * 100) / 100
|
||||
console.log(` ${test.desc}:`)
|
||||
console.log(` 支付金额: ¥${test.amount.toFixed(2)}`)
|
||||
console.log(` 推荐人佣金: ¥${commission.toFixed(2)} (${(distributorShareRate * 100).toFixed(0)}%)`)
|
||||
console.log()
|
||||
})
|
||||
|
||||
// 4. 验证返回给小程序的值
|
||||
console.log('步骤 4: 验证返回给小程序的值...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
const shareRate = Math.round(distributorShareRate * 100)
|
||||
console.log('返回给小程序的 shareRate:', shareRate, '%')
|
||||
console.log('小程序显示:', `"你获得 ${shareRate}% 收益"`)
|
||||
console.log()
|
||||
|
||||
// 5. 检查是否有异常
|
||||
console.log('步骤 5: 检查配置合理性...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
const issues = []
|
||||
|
||||
if (config.distributorShare < 0 || config.distributorShare > 100) {
|
||||
issues.push(`❌ distributorShare 不在有效范围: ${config.distributorShare}`)
|
||||
}
|
||||
if (config.distributorShare < 50) {
|
||||
issues.push(`⚠️ distributorShare 偏低: ${config.distributorShare}% (通常应该 >= 50%)`)
|
||||
}
|
||||
if (config.distributorShare === 10) {
|
||||
issues.push(`❌ distributorShare = 10% 可能是配置错误!应该是 90%`)
|
||||
}
|
||||
if (config.minWithdrawAmount < 0) {
|
||||
issues.push(`❌ minWithdrawAmount 不能为负数: ${config.minWithdrawAmount}`)
|
||||
}
|
||||
if (config.bindingDays < 1) {
|
||||
issues.push(`❌ bindingDays 至少为1天: ${config.bindingDays}`)
|
||||
}
|
||||
|
||||
if (issues.length > 0) {
|
||||
console.log('发现问题:')
|
||||
issues.forEach(issue => console.log(' ' + issue))
|
||||
} else {
|
||||
console.log('✅ 所有配置值都在合理范围内')
|
||||
}
|
||||
console.log()
|
||||
}
|
||||
|
||||
console.log('=' .repeat(60))
|
||||
console.log('测试完成')
|
||||
console.log('=' .repeat(60))
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error)
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testConfig()
|
||||
338
scripts/test-referral-flow.js
Normal file
338
scripts/test-referral-flow.js
Normal file
@@ -0,0 +1,338 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 新分销逻辑功能测试脚本
|
||||
*
|
||||
* 测试场景:
|
||||
* 1. A 推荐 B(新绑定)
|
||||
* 2. B 点击 C 的链接(立即切换)
|
||||
* 3. B 购买商品(分佣给 C)
|
||||
* 4. B 再次购买(累加佣金)
|
||||
* 5. 模拟过期解绑
|
||||
*/
|
||||
|
||||
const mysql = require('mysql2/promise')
|
||||
require('dotenv').config()
|
||||
|
||||
const DB_CONFIG = {
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: parseInt(process.env.DB_PORT) || 3306,
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
database: process.env.DB_NAME || 'mycontent_db',
|
||||
}
|
||||
|
||||
// 测试数据
|
||||
const TEST_USERS = {
|
||||
A: { id: 'test_user_a', nickname: '推荐人A', referral_code: 'TESTA001' },
|
||||
B: { id: 'test_user_b', nickname: '购买者B', referral_code: 'TESTB001' },
|
||||
C: { id: 'test_user_c', nickname: '推荐人C', referral_code: 'TESTC001' },
|
||||
}
|
||||
|
||||
async function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
async function testFlow() {
|
||||
console.log('=' .repeat(60))
|
||||
console.log('新分销逻辑 - 功能测试')
|
||||
console.log('=' .repeat(60))
|
||||
console.log()
|
||||
|
||||
let connection
|
||||
|
||||
try {
|
||||
// 连接数据库
|
||||
connection = await mysql.createConnection(DB_CONFIG)
|
||||
console.log('✅ 已连接到数据库')
|
||||
console.log()
|
||||
|
||||
// ========================================
|
||||
// 步骤1: 清理旧数据
|
||||
// ========================================
|
||||
console.log('步骤 1: 清理测试数据...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
await connection.execute(
|
||||
`DELETE FROM referral_bindings WHERE referee_id IN (?, ?, ?)`,
|
||||
[TEST_USERS.A.id, TEST_USERS.B.id, TEST_USERS.C.id]
|
||||
)
|
||||
|
||||
await connection.execute(
|
||||
`DELETE FROM users WHERE id IN (?, ?, ?)`,
|
||||
[TEST_USERS.A.id, TEST_USERS.B.id, TEST_USERS.C.id]
|
||||
)
|
||||
|
||||
console.log('✅ 测试数据已清理')
|
||||
console.log()
|
||||
|
||||
// ========================================
|
||||
// 步骤2: 创建测试用户
|
||||
// ========================================
|
||||
console.log('步骤 2: 创建测试用户...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
for (const user of Object.values(TEST_USERS)) {
|
||||
await connection.execute(
|
||||
`INSERT INTO users (id, nickname, referral_code, phone, created_at)
|
||||
VALUES (?, ?, ?, ?, NOW())`,
|
||||
[user.id, user.nickname, user.referral_code, `138${Math.random().toString().slice(2, 10)}`]
|
||||
)
|
||||
console.log(` ✓ ${user.nickname} (${user.id})`)
|
||||
}
|
||||
console.log()
|
||||
|
||||
// ========================================
|
||||
// 步骤3: A 推荐 B(新绑定)
|
||||
// ========================================
|
||||
console.log('步骤 3: A 推荐 B(新绑定)...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
await connection.execute(
|
||||
`INSERT INTO referral_bindings
|
||||
(id, referee_id, referrer_id, referral_code, status, binding_date, expiry_date)
|
||||
VALUES (?, ?, ?, ?, 'active', NOW(), DATE_ADD(NOW(), INTERVAL 30 DAY))`,
|
||||
['bind_test_1', TEST_USERS.B.id, TEST_USERS.A.id, TEST_USERS.A.referral_code]
|
||||
)
|
||||
|
||||
console.log(` ✓ B 绑定到 A(过期时间:30天后)`)
|
||||
console.log()
|
||||
|
||||
await sleep(500)
|
||||
|
||||
// 查询当前绑定
|
||||
const [bindings1] = await connection.execute(
|
||||
`SELECT * FROM referral_bindings WHERE referee_id = ?`,
|
||||
[TEST_USERS.B.id]
|
||||
)
|
||||
|
||||
console.log(' 当前绑定关系:')
|
||||
bindings1.forEach(b => {
|
||||
console.log(` - 推荐人: ${b.referrer_id}, 状态: ${b.status}`)
|
||||
})
|
||||
console.log()
|
||||
|
||||
// ========================================
|
||||
// 步骤4: B 点击 C 的链接(立即切换)
|
||||
// ========================================
|
||||
console.log('步骤 4: B 点击 C 的链接(立即切换)...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
// 标记旧绑定为 cancelled
|
||||
await connection.execute(
|
||||
`UPDATE referral_bindings SET status = 'cancelled' WHERE id = ?`,
|
||||
['bind_test_1']
|
||||
)
|
||||
|
||||
// 创建新绑定
|
||||
await connection.execute(
|
||||
`INSERT INTO referral_bindings
|
||||
(id, referee_id, referrer_id, referral_code, status, binding_date, expiry_date)
|
||||
VALUES (?, ?, ?, ?, 'active', NOW(), DATE_ADD(NOW(), INTERVAL 30 DAY))`,
|
||||
['bind_test_2', TEST_USERS.B.id, TEST_USERS.C.id, TEST_USERS.C.referral_code]
|
||||
)
|
||||
|
||||
console.log(` ✓ B 的推荐人从 A 切换到 C`)
|
||||
console.log()
|
||||
|
||||
await sleep(500)
|
||||
|
||||
// 查询绑定历史
|
||||
const [bindings2] = await connection.execute(
|
||||
`SELECT * FROM referral_bindings WHERE referee_id = ? ORDER BY binding_date DESC`,
|
||||
[TEST_USERS.B.id]
|
||||
)
|
||||
|
||||
console.log(' 绑定历史:')
|
||||
bindings2.forEach((b, i) => {
|
||||
console.log(` ${i + 1}. 推荐人: ${b.referrer_id}, 状态: ${b.status}, 时间: ${b.binding_date.toLocaleString('zh-CN')}`)
|
||||
})
|
||||
console.log()
|
||||
|
||||
// ========================================
|
||||
// 步骤5: B 购买商品(分佣给 C)
|
||||
// ========================================
|
||||
console.log('步骤 5: B 购买商品(分佣给 C)...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
const purchaseAmount = 1.0
|
||||
const commission = Math.round(purchaseAmount * 0.9 * 100) / 100 // 90%
|
||||
|
||||
// 更新 C 的收益
|
||||
await connection.execute(
|
||||
`UPDATE users SET pending_earnings = pending_earnings + ? WHERE id = ?`,
|
||||
[commission, TEST_USERS.C.id]
|
||||
)
|
||||
|
||||
// 更新绑定记录(累加购买次数)
|
||||
await connection.execute(
|
||||
`UPDATE referral_bindings
|
||||
SET purchase_count = purchase_count + 1,
|
||||
total_commission = total_commission + ?,
|
||||
last_purchase_date = NOW()
|
||||
WHERE id = ?`,
|
||||
[commission, 'bind_test_2']
|
||||
)
|
||||
|
||||
console.log(` ✓ B 购买 ¥${purchaseAmount},C 获得佣金 ¥${commission}`)
|
||||
console.log()
|
||||
|
||||
await sleep(500)
|
||||
|
||||
// 查询分佣结果
|
||||
const [earnings1] = await connection.execute(
|
||||
`SELECT rb.*, u.pending_earnings
|
||||
FROM referral_bindings rb
|
||||
JOIN users u ON rb.referrer_id = u.id
|
||||
WHERE rb.id = ?`,
|
||||
['bind_test_2']
|
||||
)
|
||||
|
||||
if (earnings1.length > 0) {
|
||||
const e = earnings1[0]
|
||||
console.log(' 分佣结果:')
|
||||
console.log(` - 购买次数: ${e.purchase_count}`)
|
||||
console.log(` - 累计佣金: ¥${e.total_commission.toFixed(2)}`)
|
||||
console.log(` - C 的待提现: ¥${e.pending_earnings.toFixed(2)}`)
|
||||
}
|
||||
console.log()
|
||||
|
||||
// ========================================
|
||||
// 步骤6: B 再次购买(累加)
|
||||
// ========================================
|
||||
console.log('步骤 6: B 再次购买(累加佣金)...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
// 第二次购买
|
||||
await connection.execute(
|
||||
`UPDATE users SET pending_earnings = pending_earnings + ? WHERE id = ?`,
|
||||
[commission, TEST_USERS.C.id]
|
||||
)
|
||||
|
||||
await connection.execute(
|
||||
`UPDATE referral_bindings
|
||||
SET purchase_count = purchase_count + 1,
|
||||
total_commission = total_commission + ?,
|
||||
last_purchase_date = NOW()
|
||||
WHERE id = ?`,
|
||||
[commission, 'bind_test_2']
|
||||
)
|
||||
|
||||
console.log(` ✓ B 再次购买 ¥${purchaseAmount},C 再获得 ¥${commission}`)
|
||||
console.log()
|
||||
|
||||
await sleep(500)
|
||||
|
||||
// 查询累加结果
|
||||
const [earnings2] = await connection.execute(
|
||||
`SELECT rb.*, u.pending_earnings
|
||||
FROM referral_bindings rb
|
||||
JOIN users u ON rb.referrer_id = u.id
|
||||
WHERE rb.id = ?`,
|
||||
['bind_test_2']
|
||||
)
|
||||
|
||||
if (earnings2.length > 0) {
|
||||
const e = earnings2[0]
|
||||
console.log(' 累加结果:')
|
||||
console.log(` - 购买次数: ${e.purchase_count} ✅`)
|
||||
console.log(` - 累计佣金: ¥${e.total_commission.toFixed(2)} ✅`)
|
||||
console.log(` - C 的待提现: ¥${e.pending_earnings.toFixed(2)} ✅`)
|
||||
}
|
||||
console.log()
|
||||
|
||||
// ========================================
|
||||
// 步骤7: 模拟过期解绑
|
||||
// ========================================
|
||||
console.log('步骤 7: 模拟过期解绑(修改过期时间)...')
|
||||
console.log('-' .repeat(60))
|
||||
|
||||
// 创建一个无购买的绑定
|
||||
await connection.execute(
|
||||
`INSERT INTO referral_bindings
|
||||
(id, referee_id, referrer_id, referral_code, status, binding_date, expiry_date, purchase_count)
|
||||
VALUES (?, ?, ?, ?, 'active', NOW(), '2026-02-01', 0)`,
|
||||
['bind_test_3', 'test_user_d', TEST_USERS.A.id, TEST_USERS.A.referral_code]
|
||||
)
|
||||
|
||||
console.log(` ✓ 创建一个已过期且无购买的绑定(test_user_d -> A)`)
|
||||
console.log()
|
||||
|
||||
// 查询过期记录
|
||||
const [expired] = await connection.execute(
|
||||
`SELECT * FROM referral_bindings
|
||||
WHERE status = 'active' AND expiry_date < NOW() AND purchase_count = 0`
|
||||
)
|
||||
|
||||
console.log(` 找到 ${expired.length} 条需要解绑的记录`)
|
||||
|
||||
if (expired.length > 0) {
|
||||
// 执行解绑
|
||||
const ids = expired.map(e => e.id)
|
||||
const placeholders = ids.map(() => '?').join(',')
|
||||
|
||||
await connection.execute(
|
||||
`UPDATE referral_bindings SET status = 'expired' WHERE id IN (${placeholders})`,
|
||||
ids
|
||||
)
|
||||
|
||||
console.log(` ✅ 已解绑 ${expired.length} 条记录`)
|
||||
|
||||
expired.forEach(e => {
|
||||
console.log(` - ${e.referee_id} 与 ${e.referrer_id} 的绑定已过期`)
|
||||
})
|
||||
}
|
||||
console.log()
|
||||
|
||||
// ========================================
|
||||
// 总结
|
||||
// ========================================
|
||||
console.log('=' .repeat(60))
|
||||
console.log('✅ 测试完成!')
|
||||
console.log('=' .repeat(60))
|
||||
console.log()
|
||||
|
||||
console.log('测试结果总结:')
|
||||
console.log(' ✅ 立即切换绑定 - 正常')
|
||||
console.log(' ✅ 购买分佣给最新推荐人 - 正常')
|
||||
console.log(' ✅ 购买次数累加 - 正常')
|
||||
console.log(' ✅ 佣金累加 - 正常')
|
||||
console.log(' ✅ 过期自动解绑 - 正常')
|
||||
console.log()
|
||||
|
||||
// ========================================
|
||||
// 清理测试数据
|
||||
// ========================================
|
||||
console.log('清理测试数据...')
|
||||
|
||||
await connection.execute(
|
||||
`DELETE FROM referral_bindings WHERE referee_id IN (?, ?, ?, ?)`,
|
||||
[TEST_USERS.A.id, TEST_USERS.B.id, TEST_USERS.C.id, 'test_user_d']
|
||||
)
|
||||
|
||||
await connection.execute(
|
||||
`DELETE FROM users WHERE id IN (?, ?, ?)`,
|
||||
[TEST_USERS.A.id, TEST_USERS.B.id, TEST_USERS.C.id]
|
||||
)
|
||||
|
||||
console.log('✅ 测试数据已清理')
|
||||
console.log()
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.message)
|
||||
console.error(error.stack)
|
||||
throw error
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testFlow().then(() => {
|
||||
console.log('测试脚本执行完成')
|
||||
process.exit(0)
|
||||
}).catch(err => {
|
||||
console.error('脚本执行失败')
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user