ThinkAdmin/plugin/think-plugs-wemall/stc/database/20241010000010_fix_wemall_constraints.php

265 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
/**
* +----------------------------------------------------------------------
* | ThinkAdmin Plugin for ThinkAdmin
* +----------------------------------------------------------------------
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
* +----------------------------------------------------------------------
* | 官方网站: https://thinkadmin.top
* +----------------------------------------------------------------------
* | 开源协议 ( https://mit-license.org )
* | 免责声明 ( https://thinkadmin.top/disclaimer )
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
* +----------------------------------------------------------------------
* | gitee 代码仓库https://gitee.com/zoujingli/ThinkAdmin
* | github 代码仓库https://github.com/zoujingli/ThinkAdmin
* +----------------------------------------------------------------------
*/
use think\migration\Migrator;
@set_time_limit(0);
@ini_set('memory_limit', '-1');
class FixWemallConstraints extends Migrator
{
/**
* 获取脚本名称.
*/
public function getName(): string
{
return 'FixWemallConstraints';
}
/**
* 修复数据库约束
*/
public function change()
{
// 为返佣记录表添加外键约束和业务字段
$this->_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<string, array<string, mixed>>
*/
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<string, array<string, mixed>> $indexes
* @param array<int, string> $columns
* @param array<string, int> $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<string, array<string, mixed>> $indexes
* @param array<int, string> $columns
* @return array<int, string>
*/
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<int, string> $columns
* @param array<string, int> $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);
}
}