Ver código fonte

fix: 资金关键表增加唯一索引,为支付/退款/结算提供幂等兜底

t_pay_log.out_trade_no:同一笔微信支付只允许入账一次
t_wallet_detail(order_no,type):同一订单同类型流水唯一
t_split_record(trade_no,type,from,to):同一交易同类型同流向分账唯一
脚本幂等可重复执行,执行前需先跑查重SQL

Co-Authored-By: Claude <noreply@anthropic.com>
skyline 3 semanas atrás
pai
commit
310bde8039

+ 69 - 0
car-wash-entity/src/main/resources/sql/v20_add_finance_idempotency_indexes.sql

@@ -0,0 +1,69 @@
+-- ====================================================
+-- v20: 资金关键表唯一索引(幂等兜底)
+-- 目的:为支付回调 / 退款回调 / 订单结算提供数据库层幂等兜底,
+--       防止并发回调、消息重投导致重复入账 / 重复扣减 / 重复分账。
+-- 涉及表:t_pay_log、t_wallet_detail、t_split_record
+--
+-- 执行前提(重要):
+--   1. 生产执行前先运行下方"查重"SQL,确认无重复数据;
+--      若存在重复,需先人工清理后再执行本脚本(否则建索引会失败)。
+--   2. 建议在业务低峰期执行(大表加唯一索引会锁表/耗时)。
+-- ====================================================
+
+-- ----------------------------
+-- 0. 查重检查(执行前先跑,以下 3 条应全部返回 0 行或空集)
+-- ----------------------------
+-- SELECT out_trade_no, COUNT(*) FROM t_pay_log GROUP BY out_trade_no HAVING COUNT(*) > 1;
+-- SELECT order_no, type, COUNT(*) FROM t_wallet_detail GROUP BY order_no, type HAVING COUNT(*) > 1;
+-- SELECT trade_no, type, from_station_id, to_station_id, COUNT(*) FROM t_split_record
+--     GROUP BY trade_no, type, from_station_id, to_station_id HAVING COUNT(*) > 1;
+
+-- ----------------------------
+-- 1. 幂等建唯一索引
+-- ----------------------------
+DROP PROCEDURE IF EXISTS add_finance_idempotency_indexes;
+DELIMITER //
+CREATE PROCEDURE add_finance_idempotency_indexes()
+BEGIN
+    -- t_pay_log.out_trade_no:微信商户订单号唯一,同一笔支付只允许处理一次
+    IF NOT EXISTS (
+        SELECT 1 FROM information_schema.STATISTICS
+        WHERE TABLE_SCHEMA = DATABASE()
+          AND TABLE_NAME = 't_pay_log'
+          AND INDEX_NAME = 'uk_out_trade_no'
+    ) THEN
+        ALTER TABLE `t_pay_log` ADD UNIQUE KEY `uk_out_trade_no` (`out_trade_no`);
+    END IF;
+
+    -- t_wallet_detail(order_no, type):同一订单号同类型的钱包流水唯一
+    IF NOT EXISTS (
+        SELECT 1 FROM information_schema.STATISTICS
+        WHERE TABLE_SCHEMA = DATABASE()
+          AND TABLE_NAME = 't_wallet_detail'
+          AND INDEX_NAME = 'uk_order_no_type'
+    ) THEN
+        ALTER TABLE `t_wallet_detail` ADD UNIQUE KEY `uk_order_no_type` (`order_no`, `type`);
+    END IF;
+
+    -- t_split_record(trade_no, type, from_station_id, to_station_id):
+    -- 同一交易同类型同流向的分账记录唯一(跨店 EXPEND/INCOME 两条 type 不同,互不冲突)
+    IF NOT EXISTS (
+        SELECT 1 FROM information_schema.STATISTICS
+        WHERE TABLE_SCHEMA = DATABASE()
+          AND TABLE_NAME = 't_split_record'
+          AND INDEX_NAME = 'uk_trade_no_type_from_to'
+    ) THEN
+        ALTER TABLE `t_split_record`
+            ADD UNIQUE KEY `uk_trade_no_type_from_to` (`trade_no`, `type`, `from_station_id`, `to_station_id`);
+    END IF;
+END //
+DELIMITER ;
+CALL add_finance_idempotency_indexes();
+DROP PROCEDURE IF EXISTS add_finance_idempotency_indexes;
+
+-- ====================================================
+-- 验证:
+--   SHOW INDEX FROM t_pay_log WHERE Key_name = 'uk_out_trade_no';
+--   SHOW INDEX FROM t_wallet_detail WHERE Key_name = 'uk_order_no_type';
+--   SHOW INDEX FROM t_split_record WHERE Key_name = 'uk_trade_no_type_from_to';
+-- ====================================================