合并 internal → main: 退款/提现/激活/CI 全面优化 #4

Open
shanshanzhong147 wants to merge 76 commits from internal into main
2 changed files with 197 additions and 0 deletions
Showing only changes of commit 077dba3d98 - Show all commits
+2
View File
@@ -0,0 +1,2 @@
*.csv
*.tsv
+195
View File
@@ -0,0 +1,195 @@
-- ============================================================================
-- migration_v3.sql
-- 提现迁移 + 佣金余额闭环修复
-- ============================================================================
--
-- 目的:
-- 1. 把 ticket(提现工单) 表里 status=4 的 185 条记录全量重迁到 withdrawals 表
-- - 删除 5 条历史错迁的 status=3 (取消/拒绝) 单
-- - 补迁 8 条历史漏迁的 status=4 (已通过) 单
-- 2. 修正 13 个 user.commission 字段,使其等于 SUM(全部 type=33 日志)
-- 3. content 写入 '历史提现 #<ticket_id>',支持反查 ticket 源头
--
-- 执行方式(注意 --default-character-set 必须显式声明):
-- docker exec -i ppanel-mysql mysql --default-character-set=utf8mb4 \
-- -uroot -pppanel_dev ppanel < migration_v3.sql
--
-- 回滚方式(假设 COMMIT 之后想恢复):
-- TRUNCATE TABLE withdrawals;
-- INSERT INTO withdrawals SELECT * FROM withdrawals_backup_v3;
-- UPDATE user u JOIN user_commission_backup_v3 b ON b.id=u.id SET u.commission = b.commission;
-- ============================================================================
-- ----------------------------------------------------------------------------
-- 阶段 0: 备份(在事务外执行,即使后续 ROLLBACK 备份也保留)
-- ----------------------------------------------------------------------------
DROP TABLE IF EXISTS withdrawals_backup_v3;
CREATE TABLE withdrawals_backup_v3 AS SELECT * FROM withdrawals;
DROP TABLE IF EXISTS user_commission_backup_v3;
CREATE TABLE user_commission_backup_v3 AS
SELECT id, commission, NOW() AS backup_at FROM user WHERE commission <> 0;
SELECT 'backup_done' AS step,
(SELECT COUNT(*) FROM withdrawals_backup_v3) AS withdrawals_rows,
(SELECT COUNT(*) FROM user_commission_backup_v3) AS user_commission_rows;
-- ----------------------------------------------------------------------------
-- 阶段 1: 事务开始
-- ----------------------------------------------------------------------------
START TRANSACTION;
-- ----------------------------------------------------------------------------
-- 阶段 2: 清空 withdrawals 表
-- ----------------------------------------------------------------------------
TRUNCATE TABLE withdrawals;
-- ----------------------------------------------------------------------------
-- 阶段 3: 从 ticket 表全量重迁(严格只迁 status=4)
-- 中文字面量统一 COLLATE utf8mb4_general_ci 跟 ticket 表一致
-- ----------------------------------------------------------------------------
INSERT INTO withdrawals (
user_id, amount, content, status, reason,
method, account, qr_code_url, created_at, updated_at
)
SELECT
t.user_id,
ROUND(CAST(TRIM(REPLACE(REPLACE(t.title, _utf8mb4'提现-' COLLATE utf8mb4_general_ci, ''),
_utf8mb4'提现' COLLATE utf8mb4_general_ci, '')) AS DECIMAL(18,2)) * 100) AS amount_cents,
CONCAT(_utf8mb4'历史提现 #' COLLATE utf8mb4_0900_ai_ci, t.id) AS content,
1 AS status,
'' AS reason,
CASE
WHEN t.description LIKE _utf8mb4'支付宝%' COLLATE utf8mb4_general_ci THEN 1
WHEN t.description LIKE _utf8mb4'微信%' COLLATE utf8mb4_general_ci THEN 2
WHEN UPPER(t.description) LIKE 'USDT%' OR UPPER(t.description) LIKE 'TRC20%' THEN 3
ELSE 0
END AS method,
CASE
WHEN UPPER(t.description) LIKE 'USDT(TRC20)-%'
THEN TRIM(SUBSTRING(t.description, LOCATE('-', t.description) + 1))
WHEN UPPER(t.description) LIKE 'USDT%' AND LOCATE('-', t.description) > 0
THEN TRIM(SUBSTRING(t.description, LOCATE('-', t.description) + 1))
WHEN t.description LIKE _utf8mb4'支付宝-data:image%' COLLATE utf8mb4_general_ci THEN _utf8mb4'支付宝收款码' COLLATE utf8mb4_0900_ai_ci
WHEN t.description LIKE _utf8mb4'微信-data:image%' COLLATE utf8mb4_general_ci THEN _utf8mb4'微信收款码' COLLATE utf8mb4_0900_ai_ci
WHEN CHAR_LENGTH(t.description) > 255 THEN LEFT(t.description, 255)
ELSE t.description
END AS account,
'' AS qr_code_url,
t.created_at,
t.updated_at
FROM ticket t
WHERE t.title LIKE _utf8mb4'提现%' COLLATE utf8mb4_general_ci
AND t.status = 4
AND TRIM(REPLACE(REPLACE(t.title, _utf8mb4'提现-' COLLATE utf8mb4_general_ci, ''),
_utf8mb4'提现' COLLATE utf8mb4_general_ci, '')) REGEXP '^[0-9]+([.][0-9]+)?$'
AND ROUND(CAST(TRIM(REPLACE(REPLACE(t.title, _utf8mb4'提现-' COLLATE utf8mb4_general_ci, ''),
_utf8mb4'提现' COLLATE utf8mb4_general_ci, '')) AS DECIMAL(18,2)) * 100) > 0
ORDER BY t.id;
-- ----------------------------------------------------------------------------
-- 阶段 4: 修正用户的 commission 字段,使其等于 SUM(type=33 日志)
-- ----------------------------------------------------------------------------
UPDATE user u
JOIN (
SELECT object_id, SUM(CAST(JSON_EXTRACT(content,'$.amount') AS SIGNED)) AS log_sum
FROM system_logs
WHERE type = 33
GROUP BY object_id
) s ON s.object_id = u.id
SET u.commission = s.log_sum
WHERE u.commission <> s.log_sum;
-- ----------------------------------------------------------------------------
-- 阶段 4b: 处理"前日志时代"账号 — 有 commission 余额但 system_logs 完全没记录
-- 补一条 type=335 admin_adjust 日志记录历史基准余额,让账目闭环
-- content 标记 source=pre_log_baseline 便于追溯
-- ----------------------------------------------------------------------------
INSERT INTO system_logs (type, date, object_id, content, created_at)
SELECT 33,
DATE_FORMAT(u.created_at, '%Y-%m-%d'),
u.id,
JSON_OBJECT(
'type', 335,
'amount', u.commission,
'order_no', '',
'timestamp', UNIX_TIMESTAMP(u.created_at)*1000,
'source', 'pre_log_baseline'
),
u.created_at
FROM user u
WHERE u.commission <> 0
AND NOT EXISTS (
SELECT 1 FROM system_logs s WHERE s.type=33 AND s.object_id=u.id
);
-- ----------------------------------------------------------------------------
-- 阶段 5: 验证(任何一项 verdict=FAIL 都应 ROLLBACK)
-- ----------------------------------------------------------------------------
-- 5a. withdrawals 总数 = ticket status=4 数,且都 > 0
SELECT
'5a_count_match' AS check_name,
(SELECT COUNT(*) FROM ticket WHERE title LIKE _utf8mb4'提现%' COLLATE utf8mb4_general_ci AND status=4
AND TRIM(REPLACE(REPLACE(title, _utf8mb4'提现-' COLLATE utf8mb4_general_ci, ''),
_utf8mb4'提现' COLLATE utf8mb4_general_ci, '')) REGEXP '^[0-9]+([.][0-9]+)?$') AS expected,
(SELECT COUNT(*) FROM withdrawals) AS actual,
IF(
(SELECT COUNT(*) FROM withdrawals) > 0
AND (SELECT COUNT(*) FROM ticket WHERE title LIKE _utf8mb4'提现%' COLLATE utf8mb4_general_ci AND status=4
AND TRIM(REPLACE(REPLACE(title, _utf8mb4'提现-' COLLATE utf8mb4_general_ci, ''),
_utf8mb4'提现' COLLATE utf8mb4_general_ci, '')) REGEXP '^[0-9]+([.][0-9]+)?$')
= (SELECT COUNT(*) FROM withdrawals),
'PASS', 'FAIL'
) AS verdict;
-- 5b. 没有 amount<=0
SELECT '5b_amount_positive' AS check_name,
0 AS expected,
(SELECT COUNT(*) FROM withdrawals WHERE amount <= 0) AS actual,
IF((SELECT COUNT(*) FROM withdrawals WHERE amount <= 0) = 0, 'PASS', 'FAIL') AS verdict;
-- 5c. content 必须形如 '历史提现 #<digits>'
SELECT '5c_content_format' AS check_name,
0 AS expected,
(SELECT COUNT(*) FROM withdrawals
WHERE content NOT REGEXP CONCAT('^', _utf8mb4'历史提现 #' COLLATE utf8mb4_0900_ai_ci, '[0-9]+$')) AS actual,
IF((SELECT COUNT(*) FROM withdrawals
WHERE content NOT REGEXP CONCAT('^', _utf8mb4'历史提现 #' COLLATE utf8mb4_0900_ai_ci, '[0-9]+$')) = 0,
'PASS', 'FAIL') AS verdict;
-- 5d. user.commission == SUM(type=33 logs) 全用户闭环
SELECT '5d_balance_closure' AS check_name,
0 AS expected,
(SELECT COUNT(*) FROM user u
LEFT JOIN (SELECT object_id, SUM(CAST(JSON_EXTRACT(content,'$.amount') AS SIGNED)) amt
FROM system_logs WHERE type=33 GROUP BY object_id) s ON s.object_id=u.id
WHERE u.commission <> COALESCE(s.amt, 0)) AS actual,
IF((SELECT COUNT(*) FROM user u
LEFT JOIN (SELECT object_id, SUM(CAST(JSON_EXTRACT(content,'$.amount') AS SIGNED)) amt
FROM system_logs WHERE type=33 GROUP BY object_id) s ON s.object_id=u.id
WHERE u.commission <> COALESCE(s.amt, 0)) = 0, 'PASS', 'FAIL') AS verdict;
-- 5e. 31742 闭环具体值核对
SELECT '5e_user_31742' AS check_name,
(SELECT COALESCE(SUM(CAST(JSON_EXTRACT(content,'$.amount') AS SIGNED)),0)
FROM system_logs WHERE type=33 AND object_id=31742) AS expected_from_logs,
(SELECT commission FROM user WHERE id=31742) AS actual_balance,
(SELECT COUNT(*) FROM withdrawals WHERE user_id=31742) AS withdrawals_count;
-- ----------------------------------------------------------------------------
-- 阶段 6: 提交事务
-- 检查上面所有 verdict 是 PASS 后才 COMMIT,否则 ROLLBACK
-- ----------------------------------------------------------------------------
COMMIT;
-- ----------------------------------------------------------------------------
-- 阶段 7: 事后摘要
-- ----------------------------------------------------------------------------
SELECT 'final_summary' AS section,
(SELECT COUNT(*) FROM withdrawals) AS withdrawals_count,
(SELECT SUM(amount) FROM withdrawals WHERE status=1) AS total_amount_cents,
(SELECT COUNT(*) FROM user u
LEFT JOIN (SELECT object_id, SUM(CAST(JSON_EXTRACT(content,'$.amount') AS SIGNED)) amt
FROM system_logs WHERE type=33 GROUP BY object_id) s ON s.object_id=u.id
WHERE u.commission <> COALESCE(s.amt, 0)) AS imbalanced_users_after_fix;