_fix_plugin_wemall_user_rebate(); // 为余额/积分表添加来源字段 $this->_fix_plugin_payment_balance_integral(); // 为用户关系表添加索引优化 $this->_fix_plugin_wemall_user_relation(); // 为订单表添加检查约束 $this->_fix_plugin_wemall_order(); } /** * 修复返佣记录表. */ private function _fix_plugin_wemall_user_rebate() { $table = $this->table('plugin_wemall_user_rebate'); // 添加订单商品项ID字段(用于精确追踪返佣) if (!$table->hasColumn('order_item_id')) { $table->addColumn('order_item_id', 'biginteger', ['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '订单商品项ID']) ->update(); } // 添加返佣规则ID字段(用于追溯规则版本) if (!$table->hasColumn('rebate_rule_id')) { $table->addColumn('rebate_rule_id', 'biginteger', ['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '返佣规则ID']) ->update(); } // 添加金额非负约束 $this->executeModifyWithCheck('plugin_wemall_user_rebate', 'amount', "DECIMAL(20,2) NOT NULL DEFAULT '0.00'", 'amount >= 0'); } /** * 修复余额/积分表. */ private function _fix_plugin_payment_balance_integral() { // 修复余额表 $table = $this->table('plugin_payment_balance'); if (!$table->hasColumn('source_type')) { $table->addColumn('source_type', 'string', ['limit' => 50, 'default' => '', 'null' => true, 'comment' => '资金来源类型']) ->addColumn('source_id', 'string', ['limit' => 50, 'default' => '', 'null' => true, 'comment' => '资金来源ID']) ->update(); } // 修复积分表 $table = $this->table('plugin_payment_integral'); if (!$table->hasColumn('source_type')) { $table->addColumn('source_type', 'string', ['limit' => 50, 'default' => '', 'null' => true, 'comment' => '积分来源类型']) ->addColumn('source_id', 'string', ['limit' => 50, 'default' => '', 'null' => true, 'comment' => '积分来源ID']) ->update(); } // 添加金额非负约束 $this->execute("ALTER TABLE `plugin_payment_balance` MODIFY `amount` DECIMAL(20,2) NOT NULL DEFAULT '0.00'"); $this->execute("ALTER TABLE `plugin_payment_integral` MODIFY `amount` DECIMAL(20,2) NOT NULL DEFAULT '0.00'"); } /** * 修复用户关系表索引. */ private function _fix_plugin_wemall_user_relation() { $indexes = $this->getTableIndexes('plugin_wemall_user_relation'); // 将普通 path 索引收敛为前缀索引,避免重复索引 if (!$this->hasIndexDefinition($indexes, ['path'], false, ['path' => 20])) { foreach ($this->findIndexNamesByColumns($indexes, ['path']) as $name) { $this->execute("ALTER TABLE `plugin_wemall_user_relation` DROP INDEX `{$name}`"); } $this->execute('ALTER TABLE `plugin_wemall_user_relation` ADD INDEX `idx_path_prefix` (`path`(20))'); $indexes = $this->getTableIndexes('plugin_wemall_user_relation'); } // 为代理层级字段补齐单列索引,已有同定义索引时跳过 foreach (['puid1', 'puid2', 'puid3'] as $column) { if (!$this->hasIndexDefinition($indexes, [$column])) { $this->execute("ALTER TABLE `plugin_wemall_user_relation` ADD INDEX `idx_{$column}` (`{$column}`)"); $indexes = $this->getTableIndexes('plugin_wemall_user_relation'); } } } /** * 修复订单表约束 */ private function _fix_plugin_wemall_order() { // 添加金额字段的非负约束 $amount_fields = [ 'amount_cost', 'amount_real', 'amount_total', 'amount_goods', 'amount_profit', 'amount_reduct', 'amount_balance', 'amount_integral', 'amount_payment', 'amount_express', 'amount_discount', 'coupon_amount', 'allow_balance', 'allow_integral', 'ratio_integral', 'rebate_amount', 'reward_balance', 'reward_integral', 'payment_amount', ]; foreach ($amount_fields as $field) { $this->executeModifyWithCheck('plugin_wemall_order', $field, "DECIMAL(20,2) NOT NULL DEFAULT '0.00'", "{$field} >= 0"); } // 添加状态字段的枚举约束 $this->executeModifyWithCheck('plugin_wemall_order', 'status', 'TINYINT NOT NULL DEFAULT 1', 'status BETWEEN 0 AND 7'); $this->executeModifyWithCheck('plugin_wemall_order', 'refund_status', 'TINYINT NOT NULL DEFAULT 0', 'refund_status BETWEEN 0 AND 7'); $this->executeModifyWithCheck('plugin_wemall_order', 'payment_status', 'TINYINT NOT NULL DEFAULT 0', 'payment_status BETWEEN 0 AND 2'); $this->executeModifyWithCheck('plugin_wemall_order', 'delivery_type', 'TINYINT NOT NULL DEFAULT 0', 'delivery_type BETWEEN 0 AND 1'); } private function executeModifyWithCheck(string $table, string $field, string $definition, string $check): void { $sql = "ALTER TABLE `{$table}` MODIFY `{$field}` {$definition}"; if ($this->supportsCheckConstraint()) { $sql .= " CHECK ({$check})"; } $this->execute($sql); } private function supportsCheckConstraint(): bool { static $supports = null; if ($supports !== null) { return $supports; } $row = $this->getAdapter()->fetchRow('SELECT VERSION() AS version'); $raw = (string)($row['version'] ?? reset($row) ?: '0.0.0'); preg_match('/\d+(?:\.\d+){1,2}/', $raw, $match); $version = $match[0] ?? '0.0.0'; if (stripos($raw, 'mariadb') !== false) { return $supports = version_compare($version, '10.2.1', '>='); } return $supports = version_compare($version, '8.0.16', '>='); } /** * 获取表索引结构. * @return array> */ private function getTableIndexes(string $table): array { $indexes = []; foreach ($this->fetchAll("SHOW INDEX FROM `{$table}`") as $index) { $name = strval($index['Key_name'] ?? ''); if ($name === '' || $name === 'PRIMARY') { continue; } $column = strval($index['Column_name'] ?? ''); $indexes[$name]['unique'] = intval($index['Non_unique'] ?? 1) === 0; $indexes[$name]['columns'][intval($index['Seq_in_index'] ?? 0)] = $column; if (is_numeric($index['Sub_part'] ?? null) && intval($index['Sub_part']) > 0) { $indexes[$name]['limits'][$column] = intval($index['Sub_part']); } } foreach ($indexes as $name => $index) { $columns = $index['columns'] ?? []; ksort($columns); $indexes[$name] = [ 'name' => $name, 'unique' => !empty($index['unique']), 'columns' => array_values(array_filter($columns, 'strlen')), 'limits' => $index['limits'] ?? [], ]; } return $indexes; } /** * 判断是否已存在目标索引定义. * @param array> $indexes * @param array $columns * @param array $limits */ private function hasIndexDefinition(array $indexes, array $columns, bool $unique = false, array $limits = []): bool { $expect = $this->buildIndexSignature($columns, $unique, $limits); foreach ($indexes as $index) { if ($this->buildIndexSignature($index['columns'] ?? [], !empty($index['unique']), (array)($index['limits'] ?? [])) === $expect) { return true; } } return false; } /** * 查找同列索引名称. * @param array> $indexes * @param array $columns * @return array */ private function findIndexNamesByColumns(array $indexes, array $columns): array { $columns = array_values($columns); $names = []; foreach ($indexes as $name => $index) { if (($index['columns'] ?? []) === $columns) { $names[] = $name; } } return $names; } /** * 生成索引签名用于比较. * @param array $columns * @param array $limits */ private function buildIndexSignature(array $columns, bool $unique = false, array $limits = []): string { $columns = array_values(array_filter(array_map('strval', $columns), 'strlen')); $normalized = []; foreach ($columns as $column) { if (isset($limits[$column]) && is_numeric($limits[$column])) { $normalized[$column] = intval($limits[$column]); } } return json_encode([ 'columns' => $columns, 'unique' => $unique, 'limits' => $normalized, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } }