89 lines
6.2 KiB
SQL
89 lines
6.2 KiB
SQL
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||
|
||
-- 这不是可直接混跑旧 admin 的普通滚动迁移:旧 admin 不读 operations_status,会把总体 status=pending 的新单直接财务终审。
|
||
-- 生产必须先摘流所有旧 admin,再执行 102 并部署新 admin,恢复 admin 流量后才能滚动 gateway。
|
||
|
||
-- checkpoint 在首次执行时固定迁移前的最大申请 ID。迁移中断后重放仍使用原边界,
|
||
-- 不会把部署窗口内由旧 gateway 新建的单误标为 skipped。该表只保存小型迁移元数据,不承载业务状态。
|
||
CREATE TABLE IF NOT EXISTS schema_migration_checkpoints (
|
||
version VARCHAR(128) NOT NULL,
|
||
checkpoint_value BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||
created_at_ms BIGINT NOT NULL DEFAULT 0,
|
||
PRIMARY KEY (version)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='可重放迁移的持久边界';
|
||
|
||
INSERT IGNORE INTO schema_migration_checkpoints (version, checkpoint_value, created_at_ms)
|
||
SELECT '102_user_withdrawal_operations_review.sql', COALESCE(MAX(id), 0), CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED)
|
||
FROM admin_user_withdrawal_applications;
|
||
|
||
-- 新列的数据库默认必须是 pending:旧 gateway 不传新列,在滚动窗口内也会自动进入运营审核。
|
||
-- 单个 ALTER 的列变更具有原子性;信息查询和持久 checkpoint 使整个文件在中断后可安全重放。
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'admin_user_withdrawal_applications' AND COLUMN_NAME = 'operations_status') = 0,
|
||
'ALTER TABLE admin_user_withdrawal_applications ADD COLUMN operations_status VARCHAR(32) NOT NULL DEFAULT ''pending'' COMMENT ''运营审核状态:skipped/pending/rejecting/approved/rejected'' AFTER status, ADD COLUMN operations_reviewer_user_id BIGINT UNSIGNED NULL AFTER operations_status, ADD COLUMN operations_reviewer_name VARCHAR(64) NOT NULL DEFAULT '''' AFTER operations_reviewer_user_id, ADD COLUMN operations_audit_remark TEXT NULL AFTER operations_reviewer_name, ADD COLUMN operations_audit_command_id VARCHAR(128) NOT NULL DEFAULT '''' AFTER operations_audit_remark, ADD COLUMN operations_audit_transaction_id VARCHAR(128) NOT NULL DEFAULT '''' AFTER operations_audit_command_id, ADD COLUMN operations_reviewed_at_ms BIGINT NULL AFTER operations_audit_transaction_id, ALGORITHM=INPLACE, LOCK=NONE',
|
||
'SELECT 1'
|
||
);
|
||
PREPARE stmt FROM @ddl;
|
||
EXECUTE stmt;
|
||
DEALLOCATE PREPARE stmt;
|
||
|
||
-- 兼容早期局部执行过 default=skipped 的开发库;修正默认值是元数据操作,不回写全表。
|
||
SET @ddl := IF(
|
||
(SELECT COLUMN_DEFAULT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'admin_user_withdrawal_applications' AND COLUMN_NAME = 'operations_status') <> 'pending',
|
||
'ALTER TABLE admin_user_withdrawal_applications ALTER COLUMN operations_status SET DEFAULT ''pending'', ALGORITHM=INPLACE, LOCK=NONE',
|
||
'SELECT 1'
|
||
);
|
||
PREPARE stmt FROM @ddl;
|
||
EXECUTE stmt;
|
||
DEALLOCATE PREPARE stmt;
|
||
|
||
-- 只把迁移前已经终审的历史行标为 skipped,保留 status=pending 的真实待审申请进入运营初审;
|
||
-- 按主键范围回填低频 admin 申请,不扫描 wallet/user 流水。大量存量时在低峰执行,并监控行锁和复制延迟。
|
||
UPDATE admin_user_withdrawal_applications application
|
||
JOIN schema_migration_checkpoints checkpoint
|
||
ON checkpoint.version = '102_user_withdrawal_operations_review.sql'
|
||
SET application.operations_status = 'skipped'
|
||
WHERE application.id <= checkpoint.checkpoint_value
|
||
AND application.status IN ('approved', 'rejected')
|
||
AND application.operations_status = 'pending';
|
||
|
||
-- 索引在回填后构建,避免回填期间逐行维护新索引。建索引会扫描该 admin 表并产生额外 IO/磁盘占用,
|
||
-- 必须在低峰确认磁盘余量,并监控 metadata lock 和复制延迟。
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'admin_user_withdrawal_applications' AND INDEX_NAME = 'idx_admin_withdrawal_app_operations_status_time') = 0,
|
||
'ALTER TABLE admin_user_withdrawal_applications ADD KEY idx_admin_withdrawal_app_operations_status_time (app_code, operations_status, created_at_ms, id), ALGORITHM=INPLACE, LOCK=NONE',
|
||
'SELECT 1'
|
||
);
|
||
PREPARE stmt FROM @ddl;
|
||
EXECUTE stmt;
|
||
DEALLOCATE PREPARE stmt;
|
||
|
||
SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED);
|
||
|
||
-- 财务权限保留原 code,避免存量自建角色和 JWT 契约失效;仅收敛后台展示名称并新增独立运营权限。
|
||
INSERT INTO admin_permissions (name, code, kind, description, created_at_ms, updated_at_ms) VALUES
|
||
('用户提现财务审核', 'finance-withdrawal:view', 'menu', '查看已通过运营审核或历史兼容的用户提现申请', @now_ms, @now_ms),
|
||
('用户提现财务审核操作', 'finance-withdrawal:audit', 'button', '财务终审用户提现并扣减或释放冻结余额', @now_ms, @now_ms),
|
||
('用户提现运营审核', 'operations-withdrawal:view', 'menu', '查看需要运营初审的用户提现申请', @now_ms, @now_ms),
|
||
('用户提现运营审核操作', 'operations-withdrawal:audit', 'button', '运营初审用户提现;通过后转交财务,拒绝时释放冻结余额', @now_ms, @now_ms)
|
||
ON DUPLICATE KEY UPDATE
|
||
name = VALUES(name),
|
||
kind = VALUES(kind),
|
||
description = VALUES(description),
|
||
updated_at_ms = @now_ms;
|
||
|
||
-- 固定运营岗位不能继续持有财务终审权限,否则可绕过运营初审直接结算 frozen;只清理两个精确 role code 和两个精确 permission code。
|
||
DELETE role_permission
|
||
FROM admin_role_permissions role_permission
|
||
JOIN admin_roles role ON role.id = role_permission.role_id
|
||
JOIN admin_permissions permission ON permission.id = role_permission.permission_id
|
||
WHERE role.code IN ('ops-admin', 'operations-specialist')
|
||
AND permission.code IN ('finance-withdrawal:view', 'finance-withdrawal:audit');
|
||
|
||
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
|
||
SELECT role.id, permission.id
|
||
FROM admin_roles role
|
||
JOIN admin_permissions permission
|
||
WHERE role.code IN ('platform-admin', 'ops-admin', 'operations-specialist')
|
||
AND permission.code IN ('operations-withdrawal:view', 'operations-withdrawal:audit');
|