新功能: 还原 v1/public/user/invite_sales 接口 + promo schema 修复迁移
Build docker and publish / build (20.15.1) (push) Failing after 13m47s
Build docker and publish / build (20.15.1) (pull_request) Failing after 15m7s

- 还原 /v1/public/user/invite_sales 接口及 /invite/sales 别名(与 invite_records 并存)
  逻辑/handler/types 与 197fed7d 删除前版本完全一致,按 fefbd4f5 新 routes 结构注册
- 新增迁移 02155_promo_schema_fix: 幂等修复 subscribe_promo / order 列类型与索引偏差
- 同步 etc/ppanel.yaml 数据库连接配置
- 补齐相关需求与设计文档
This commit is contained in:
2026-05-28 20:46:37 -07:00
parent fefbd4f56a
commit 3e265bd837
12 changed files with 2418 additions and 2 deletions
@@ -0,0 +1,6 @@
-- 02155 down
--
-- 本迁移只是把 02154 的偏差修正回它应有的目标定义,没有引入新的列/表。
-- 回滚 02155 并不应该把列重新改坏成 varchar/INT NULL 的旧偏差,因此 down
-- 为空操作。若需彻底删除 promo 系统,请回滚到 02154 的 down。
SELECT '02155 has no destructive forward step; down is a no-op.';
@@ -0,0 +1,241 @@
-- 02155 Promo Schema Fix
--
-- 修复历史环境中 02154 未正确执行(或部分 GORM AutoMigrate 推断)导致的
-- promo 系统列类型 / 索引偏差。完全幂等:可重复执行。
--
-- 覆盖偏差:
-- 1) subscribe_promo.quantity 实际 int/NULL -> BIGINT NOT NULL DEFAULT 1
-- 2) subscribe_promo 唯一索引 实际 (subscribe_id, promo_rule_id) -> (subscribe_id, quantity, promo_rule_id)
-- 3) order.promo_rule_id 实际 int/NULL -> BIGINT UNSIGNED NOT NULL DEFAULT 0
-- 4) order.promo_discount 实际 varchar(255)/NULL -> BIGINT NOT NULL DEFAULT 0
--
-- 设计原则:
-- - 所有 ALTER 前先做 NULL/空串兜底,避免 NOT NULL 转换失败。
-- - 类型已经正确的环境(02154 正常跑过)不会被改动,所有 IF 判断都基于
-- INFORMATION_SCHEMA 当前真实状态。
-- - 索引差异处理 4 个分支:仅当索引确实是错的旧形态时才替换,已经是新形态则不动。
-- ============================================================================
-- 1) subscribe_promo.quantity
-- ============================================================================
-- 1.1 列不存在则补建(极端历史环境兜底)
SET @col_exists = (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'subscribe_promo'
AND COLUMN_NAME = 'quantity'
);
SET @sql = IF(
@col_exists = 0,
'ALTER TABLE `subscribe_promo` ADD COLUMN `quantity` BIGINT NOT NULL DEFAULT 1 COMMENT ''购买数量'' AFTER `subscribe_id`',
'SELECT ''subscribe_promo.quantity exists, skip ADD'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 1.2 NULL 兜底为 1(旧 AutoMigrate 推断列允许 NULL,必须先回填再 NOT NULL
UPDATE `subscribe_promo` SET `quantity` = 1 WHERE `quantity` IS NULL;
-- 1.3 类型 / 可空 / 默认值修正:只在与目标定义不一致时改
SET @col_def_wrong = (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'subscribe_promo'
AND COLUMN_NAME = 'quantity'
AND (
LOWER(DATA_TYPE) <> 'bigint'
OR IS_NULLABLE = 'YES'
OR COLUMN_DEFAULT IS NULL
OR COLUMN_DEFAULT <> '1'
)
);
SET @sql = IF(
@col_def_wrong = 1,
'ALTER TABLE `subscribe_promo` MODIFY COLUMN `quantity` BIGINT NOT NULL DEFAULT 1 COMMENT ''购买数量''',
'SELECT ''subscribe_promo.quantity already matches target definition'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ============================================================================
-- 2) subscribe_promo 唯一索引:旧形态 -> (subscribe_id, quantity, promo_rule_id)
-- ============================================================================
-- 2.1 删除已知的所有旧形态唯一索引(如果存在)
SET @idx_exists = (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'subscribe_promo'
AND INDEX_NAME = 'idx_subscribe_rule'
);
SET @sql = IF(
@idx_exists = 1,
'ALTER TABLE `subscribe_promo` DROP INDEX `idx_subscribe_rule`',
'SELECT ''subscribe_promo.idx_subscribe_rule absent'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @idx_exists = (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'subscribe_promo'
AND INDEX_NAME = 'uk_subscribe_rule'
);
SET @sql = IF(
@idx_exists = 1,
'ALTER TABLE `subscribe_promo` DROP INDEX `uk_subscribe_rule`',
'SELECT ''subscribe_promo.uk_subscribe_rule absent'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @idx_exists = (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'subscribe_promo'
AND INDEX_NAME = 'uk_subscribe_qty_rule'
);
SET @sql = IF(
@idx_exists = 1,
'ALTER TABLE `subscribe_promo` DROP INDEX `uk_subscribe_qty_rule`',
'SELECT ''subscribe_promo.uk_subscribe_qty_rule absent'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 2.2 新建目标唯一索引(缺失时才建)
SET @idx_exists = (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'subscribe_promo'
AND INDEX_NAME = 'uk_subscribe_quantity_rule'
);
SET @sql = IF(
@idx_exists = 0,
'ALTER TABLE `subscribe_promo` ADD UNIQUE KEY `uk_subscribe_quantity_rule` (`subscribe_id`, `quantity`, `promo_rule_id`)',
'SELECT ''subscribe_promo.uk_subscribe_quantity_rule exists'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ============================================================================
-- 3) order.promo_rule_id -> BIGINT UNSIGNED NOT NULL DEFAULT 0
-- ============================================================================
SET @col_exists = (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'order'
AND COLUMN_NAME = 'promo_rule_id'
);
SET @sql = IF(
@col_exists = 0,
'ALTER TABLE `order` ADD COLUMN `promo_rule_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''促销规则ID, 0=未使用促销'' AFTER `discount`',
'SELECT ''order.promo_rule_id exists, skip ADD'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `order` SET `promo_rule_id` = 0 WHERE `promo_rule_id` IS NULL;
SET @col_def_wrong = (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'order'
AND COLUMN_NAME = 'promo_rule_id'
AND (
LOWER(DATA_TYPE) <> 'bigint'
OR INSTR(LOWER(COLUMN_TYPE), 'unsigned') = 0
OR IS_NULLABLE = 'YES'
OR COLUMN_DEFAULT IS NULL
OR COLUMN_DEFAULT <> '0'
)
);
SET @sql = IF(
@col_def_wrong = 1,
'ALTER TABLE `order` MODIFY COLUMN `promo_rule_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''促销规则ID, 0=未使用促销''',
'SELECT ''order.promo_rule_id already matches target definition'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ============================================================================
-- 4) order.promo_discount -> BIGINT NOT NULL DEFAULT 0
-- 历史 AutoMigrate 推断为 varchar(255)/NULL,金额字段错存为字符串。
-- 必须先把空串/NULL 兜底为 '0',再 MODIFY,否则 MySQL 转 BIGINT 会写 0
-- (这里我们仍兜底显式化,避免触发 strict mode 报错)。
-- ============================================================================
SET @col_exists = (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'order'
AND COLUMN_NAME = 'promo_discount'
);
SET @sql = IF(
@col_exists = 0,
'ALTER TABLE `order` ADD COLUMN `promo_discount` BIGINT NOT NULL DEFAULT 0 COMMENT ''促销优惠金额(分)'' AFTER `promo_rule_id`',
'SELECT ''order.promo_discount exists, skip ADD'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 当且仅当当前是字符串型时做兜底(避免对已经是 BIGINT 的环境跑无谓 UPDATE
SET @col_is_string = (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'order'
AND COLUMN_NAME = 'promo_discount'
AND LOWER(DATA_TYPE) IN ('varchar', 'char', 'text')
);
SET @sql = IF(
@col_is_string = 1,
'UPDATE `order` SET `promo_discount` = ''0'' WHERE `promo_discount` IS NULL OR `promo_discount` = ''''',
'SELECT ''order.promo_discount not string type, skip backfill'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @col_def_wrong = (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'order'
AND COLUMN_NAME = 'promo_discount'
AND (
LOWER(DATA_TYPE) <> 'bigint'
OR IS_NULLABLE = 'YES'
OR COLUMN_DEFAULT IS NULL
OR COLUMN_DEFAULT <> '0'
)
);
SET @sql = IF(
@col_def_wrong = 1,
'ALTER TABLE `order` MODIFY COLUMN `promo_discount` BIGINT NOT NULL DEFAULT 0 COMMENT ''促销优惠金额(分)''',
'SELECT ''order.promo_discount already matches target definition'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;