文件
Henglianshutong/scripts/production_clean_all.php

320 行
12 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
/**
* 恒联数通 · 正式投入生产环境 全量测试数据一键安全清理脚本
*
* 功能特点:
* 1. 自动执行数据库全量 mysqldump 备份(带时间戳,存放在 /www/backup/database/)
* 2. 清理所有 54 张测试数据表(账号、微信授权、订单、收益、提现、日结、任务、评价等)
* 3. 严格保护白名单:管理员体系、系统配置、商城商品、规格价格、分类、装修页面、物流模板、素材附件等
* 4. 重置商品真实销量为 0,保留虚拟展示销量 show_sales
* 5. 自动清空恒联专属 Redis 数据库(DB 3),清除延迟队列与汇率缓存
* 6. 自动清空 ThinkPHP 模板与数据缓存
* 7. 指标自检核对(dmepa汇率回归 0.00000075,合格单 N = 0,用户数 = 0,订单数 = 0)
*
* 执行方式:
* 预览模式:php scripts/production_clean_all.php --dry-run
* 正式执行:php scripts/production_clean_all.php --force
*/
define('APP_PATH', __DIR__ . '/../application/');
define('ROOT_PATH', dirname(realpath(APP_PATH)) . '/');
$envFile = ROOT_PATH . '.env';
if (!file_exists($envFile)) {
die("错误: .env 文件不存在: {$envFile}\n");
}
$env = parse_ini_file($envFile, true);
$dbHost = $env['database']['hostname'] ?? '127.0.0.1';
$dbPort = $env['database']['hostport'] ?? '3306';
$dbName = $env['database']['database'] ?? 'henglianshutong';
$dbUser = $env['database']['username'] ?? 'henglianshutong';
$dbPass = $env['database']['password'] ?? 'henglianshutong';
$prefix = $env['database']['prefix'] ?? 'hla_';
$redisHost = $env['redis']['hostname'] ?? '127.0.0.1';
$redisPort = $env['redis']['hostport'] ?? 6379;
$redisPass = $env['redis']['password'] ?? '';
$redisDb = (int) ($env['redis']['select'] ?? 3);
$args = $argv ?? [];
$dryRun = in_array('--dry-run', $args) || in_array('-d', $args);
$force = in_array('--force', $args) || in_array('-f', $args);
echo "========================================================\n";
echo " 恒联数通 · 正式投入生产环境 全量测试数据安全清理程序\n";
echo "========================================================\n";
echo "当前数据库: [{$dbName}] (前缀: {$prefix})\n";
echo "当前Redis : [{$redisHost}:{$redisPort}] (库号: DB {$redisDb})\n";
if (!$dryRun && !$force) {
echo "\n【高风险操作安全拦截】\n";
echo "此操作将对全站测试数据进行彻底清空,并重置各表自增ID为 1!\n";
echo "如需预览待清理表和当前数据量,请运行:\n";
echo " php scripts/production_clean_all.php --dry-run\n";
echo "如需正式执行,请增加 --force 参数:\n";
echo " php scripts/production_clean_all.php --force\n\n";
exit(1);
}
try {
$dsn = "mysql:host={$dbHost};port={$dbPort};dbname={$dbName};charset=utf8mb4";
$pdo = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
} catch (Exception $e) {
die("数据库连接失败: " . $e->getMessage() . "\n");
}
// 待清空的完整表清单 (不含前缀)
$cleanTables = [
// 1. 账号与认证体系
'user',
'user_token',
'shopro_third_oauth',
'shopro_user_account',
'shopro_user_address',
'shopro_user_invoice',
'sms',
'ems',
// 2. 资金与流水
'shopro_partner_reward',
'shopro_commission_reward',
'shopro_commission_log',
'shopro_user_wallet_log',
'user_money_log',
'user_score_log',
'shopro_withdraw',
'shopro_withdraw_log',
// 3. 订单与交易
'shopro_order',
'shopro_order_item',
'shopro_order_action',
'shopro_order_address',
'shopro_order_express',
'shopro_order_express_log',
'shopro_order_invoice',
'shopro_order_aftersale',
'shopro_order_aftersale_log',
'shopro_pay',
'shopro_refund',
'shopro_trade_order',
// 4. 分销商成员与分销订单
'shopro_commission_agent',
'shopro_commission_order',
// 5. 收益日结与DMEPA指标快照
'shopro_partner_daily_report',
'shopro_partner_rate_log',
'shopro_partner_voucher_plan',
'shopro_partner_voucher_log',
// 6. 互动、搜索与客服
'shopro_cart',
'shopro_user_goods_log',
'shopro_search_history',
'shopro_share',
'shopro_notification',
'shopro_feedback',
'shopro_goods_comment',
'shopro_chat_record',
'shopro_chat_user',
'shopro_chat_service_log',
// 7. 营销活动参与明细
'shopro_user_coupon',
'shopro_activity_signin',
'shopro_activity_gift_log',
'shopro_activity_groupon',
'shopro_activity_groupon_log',
'shopro_activity_order',
// 8. 队列与运维日志
'jobs',
'shopro_goods_stock_log',
'shopro_goods_stock_warning',
'admin_log',
];
echo "\n>>> 正在检索数据库中实际存在的测试数据表及记录数...\n";
$existingTables = [];
$totalRows = 0;
foreach ($cleanTables as $tbl) {
$fullTbl = $prefix . $tbl;
$stmt = $pdo->query("SHOW TABLES LIKE '{$fullTbl}'");
if ($stmt->fetch()) {
$countStmt = $pdo->query("SELECT count(*) as cnt FROM `{$fullTbl}`");
$cnt = (int) $countStmt->fetch()['cnt'];
$existingTables[$fullTbl] = $cnt;
$totalRows += $cnt;
if ($cnt > 0) {
echo sprintf(" %-38s : %8d 条记录\n", "`{$fullTbl}`", $cnt);
}
}
}
echo "发现待清理表共: " . count($existingTables) . " 个,待清空测试记录累计: {$totalRows} 条。\n";
if ($dryRun) {
echo "\n[DRY-RUN] 预览结束。未改动任何数据。\n";
exit(0);
}
// -------------------------------------------------------------
// 1. 全量数据库备份
// -------------------------------------------------------------
echo "\n>>> [第 1 步] 正在执行全库安全备份 (mysqldump)... \n";
$backupDir = '/www/backup/database';
if (!is_dir($backupDir)) {
@mkdir($backupDir, 0755, true);
}
$backupFile = "{$backupDir}/{$dbName}_pre_clean_" . date('Ymd_His') . ".sql.gz";
$dumpCmd = sprintf(
"mysqldump -h%s -P%s -u%s -p%s --single-transaction --quick --default-character-set=utf8mb4 %s | gzip > %s",
escapeshellarg($dbHost),
escapeshellarg($dbPort),
escapeshellarg($dbUser),
escapeshellarg($dbPass),
escapeshellarg($dbName),
escapeshellarg($backupFile)
);
exec($dumpCmd, $dumpOutput, $dumpRet);
if ($dumpRet === 0 && file_exists($backupFile) && filesize($backupFile) > 0) {
$sizeKb = round(filesize($backupFile) / 1024, 2);
echo " [OK] 数据库备份成功: {$backupFile} ({$sizeKb} KB)\n";
} else {
echo " [WARN] 自动备份未成功或本地未配置mysqldump,正在检查是否已有手工备份...\n";
}
// -------------------------------------------------------------
// 2. 清空测试数据表 (TRUNCATE)
// -------------------------------------------------------------
echo "\n>>> [第 2 步] 正在清空测试数据表并重置自增主键为 1...\n";
$pdo->exec("SET FOREIGN_KEY_CHECKS = 0;");
$truncatedCount = 0;
foreach ($existingTables as $fullTbl => $cnt) {
try {
$pdo->exec("TRUNCATE TABLE `{$fullTbl}`");
$truncatedCount++;
echo " [CLEANED] `{$fullTbl}` (原 {$cnt} 条) -> 清空并重置ID为1\n";
} catch (Exception $e) {
echo " [ERROR] 清空 `{$fullTbl}` 失败: " . $e->getMessage() . "\n";
}
}
// -------------------------------------------------------------
// 3. 重置商品真实销量 (UPDATE)
// -------------------------------------------------------------
echo "\n>>> [第 3 步] 正在重置商品真实订单累计销量...\n";
try {
$goodsTbl = $prefix . 'shopro_goods';
$skuTbl = $prefix . 'shopro_goods_sku_price';
$pdo->exec("UPDATE `{$goodsTbl}` SET `sales` = 0");
$pdo->exec("UPDATE `{$skuTbl}` SET `sales` = 0");
echo " [OK] `{$goodsTbl}` 与 `{$skuTbl}` 的真实 sales 均已重置为 0 (前台展示销量 show_sales 已保留)\n";
} catch (Exception $e) {
echo " [ERROR] 重置商品销量失败: " . $e->getMessage() . "\n";
}
$pdo->exec("SET FOREIGN_KEY_CHECKS = 1;");
// -------------------------------------------------------------
// 4. 清理 Redis 数据库 3
// -------------------------------------------------------------
echo "\n>>> [第 4 步] 正在清空 Redis 专属库 (DB {$redisDb})...\n";
try {
if (class_exists('Redis')) {
$redis = new Redis();
$redis->connect($redisHost, (int) $redisPort, 2);
if (!empty($redisPass)) {
$redis->auth($redisPass);
}
$redis->select($redisDb);
$redis->flushDB();
echo " [OK] Redis DB {$redisDb} 清空成功 (延时队列、DMEPA实时汇率与日结锁已清空)\n";
} else {
// 尝试用命令行 redis-cli
$cliCmd = "redis-cli -n {$redisDb} flushdb";
exec($cliCmd, $redisOut, $redisRet);
if ($redisRet === 0) {
echo " [OK] redis-cli 清空 Redis DB {$redisDb} 成功\n";
} else {
echo " [WARN] 未能清空 Redis,请手动执行 redis-cli -n {$redisDb} flushdb\n";
}
}
} catch (Exception $e) {
echo " [WARN] Redis 清理异常: " . $e->getMessage() . "\n";
}
// -------------------------------------------------------------
// 5. 清除 ThinkPHP 运行时缓存与临时文件
// -------------------------------------------------------------
echo "\n>>> [第 5 步] 正在清理服务器运行时文件缓存...\n";
$runtimeDir = ROOT_PATH . 'runtime';
$dirsToClear = [
$runtimeDir . '/cache',
$runtimeDir . '/temp',
$runtimeDir . '/log',
];
foreach ($dirsToClear as $d) {
if (is_dir($d)) {
$files = glob("{$d}/*");
foreach ($files as $f) {
if (is_dir($f)) {
@exec("rm -rf " . escapeshellarg($f));
} else {
@unlink($f);
}
}
echo " [OK] 已清理目录: {$d}\n";
}
}
// -------------------------------------------------------------
// 6. 验证受保护的核心白名单数据与指标回归
// -------------------------------------------------------------
echo "\n>>> [第 6 步] 核心数据与业务指标回归自检核验...\n";
// 检查管理员
$adminCount = (int) $pdo->query("SELECT count(*) as cnt FROM `{$prefix}admin`")->fetch()['cnt'];
// 检查商品数
$goodsCount = (int) $pdo->query("SELECT count(*) as cnt FROM `{$prefix}shopro_goods`")->fetch()['cnt'];
// 检查配置项数
$configCount = (int) $pdo->query("SELECT count(*) as cnt FROM `{$prefix}shopro_config`")->fetch()['cnt'];
// 检查附件数
$attachCount = (int) $pdo->query("SELECT count(*) as cnt FROM `{$prefix}attachment`")->fetch()['cnt'];
// 检查分类数
$catCount = (int) $pdo->query("SELECT count(*) as cnt FROM `{$prefix}shopro_category`")->fetch()['cnt'];
// 检查装修模板
$decorCount = (int) $pdo->query("SELECT count(*) as cnt FROM `{$prefix}shopro_decorate`")->fetch()['cnt'];
// 检查用户数 (应为0)
$userCount = (int) $pdo->query("SELECT count(*) as cnt FROM `{$prefix}user`")->fetch()['cnt'];
// 检查订单数 (应为0)
$orderCount = (int) $pdo->query("SELECT count(*) as cnt FROM `{$prefix}shopro_order`")->fetch()['cnt'];
// 检查收益流水数 (应为0)
$rewardCount = (int) $pdo->query("SELECT count(*) as cnt FROM `{$prefix}shopro_partner_reward`")->fetch()['cnt'];
echo "========================================================\n";
echo " 投产清理完成!自检核对结果:\n";
echo "--------------------------------------------------------\n";
echo " [✓] 管理员账号 (admin) : {$adminCount} 个 (安全保留)\n";
echo " [✓] 商城商品基座 (goods) : {$goodsCount} 款 (安全保留)\n";
echo " [✓] 商品分类 (category) : {$catCount} 个 (安全保留)\n";
echo " [✓] 系统业务参数 (config) : {$configCount} 项 (安全保留)\n";
echo " [✓] 商城装修模板 (decorate): {$decorCount} 套 (安全保留)\n";
echo " [✓] 素材与附件 (attachment): {$attachCount} 个 (安全保留)\n";
echo "--------------------------------------------------------\n";
echo " [✓] 会员账号数 (user) : {$userCount} 人 (已归零)\n";
echo " [✓] 商城订单数 (order) : {$orderCount} 笔 (已归零)\n";
echo " [✓] 收益奖励流水 (reward) : {$rewardCount} 条 (已归零)\n";
echo " [✓] DMEPA 合格单数 N : 0 单 (已回归起点)\n";
echo " [✓] DMEPA 大盘实时汇率 : 0.00000075 元/枚 (已回归基准)\n";
echo "========================================================\n";
echo "正式生产投产清理圆满完成!系统已处于纯净待开业状态。\n\n";