yeg
2026-09-16 e617e479055d37e7482621d46dfa8608ddb55f07
chore(scratch): 两库合并的探针与做法留档

把本机开发库与服务器库并到一起时用到的探针、迁移前的 flyway 历史快照与做法说明一起入库,
下次再遇到库漂移不用重新摸索:结构摘要、逐行比对、差异行打印、并集插入、Flyway 的只读与迁移入口。

同时补了忽略规则:探针的临时输出,以及为分批应用迁移拷出来的迁移副本(入库会与真迁移混淆)。
备份(含会话令牌与手机号)此前已挡住。

做法与冲突判定规则见 .scratch/db-merge/README.md。
10个文件已添加
1个文件已修改
996 ■■■■■ 已修改文件
.gitignore 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/db-merge/DiffRows.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/db-merge/FlywayProbe.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/db-merge/Query.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/db-merge/README.md 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/db-merge/RowCompare.java 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/db-merge/RunSqlFile.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/db-merge/SchemaDigest.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/db-merge/SchemaDump.java 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/db-merge/SyncTool.java 235 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/db-merge/restore-history.sql 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -8,3 +8,7 @@
# 数据库备份(含会话令牌、用户手机号等)绝不提交。
.scratch/**/backup/
# 探针的临时输出,以及为「分批应用迁移」而拷的迁移副本——副本入库会与真迁移混淆。
.scratch/**/out/
.scratch/**/dryrun-migrations/
.scratch/db-merge/DiffRows.java
New file
@@ -0,0 +1,63 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
 * 只读探针:只打印某张表有差异的行——仅左库有 / 仅右库有 / 同主键但内容不同(两侧并列)。
 *
 * <p>用法:java DiffRows &lt;左url&gt; &lt;右url&gt; &lt;表名&gt; &lt;展示列,展示列...&gt;
 */
public class DiffRows {
    public static void main(String[] args) throws Exception {
        String table = args[2];
        String[] columns = args[3].split(",");
        try (Connection left = DriverManager.getConnection(args[0], "root", "123456");
             Connection right = DriverManager.getConnection(args[1], "root", "123456")) {
            Map<String, String> leftRows = rows(left, table, columns);
            Map<String, String> rightRows = rows(right, table, columns);
            System.out.println("== " + table + ":仅左库(本机 dev)有 ==");
            for (Map.Entry<String, String> entry : leftRows.entrySet()) {
                if (!rightRows.containsKey(entry.getKey())) System.out.println("  " + entry.getValue());
            }
            System.out.println("== " + table + ":仅右库(服务器)有 ==");
            for (Map.Entry<String, String> entry : rightRows.entrySet()) {
                if (!leftRows.containsKey(entry.getKey())) System.out.println("  " + entry.getValue());
            }
            System.out.println("== " + table + ":同主键内容不同 ==");
            for (Map.Entry<String, String> entry : leftRows.entrySet()) {
                String other = rightRows.get(entry.getKey());
                if (other == null || other.equals(entry.getValue())) continue;
                System.out.println("  左 " + entry.getValue());
                System.out.println("  右 " + other);
                System.out.println();
            }
        }
    }
    private static Map<String, String> rows(Connection connection, String table, String[] columns) throws Exception {
        Map<String, String> lines = new TreeMap<>();
        String selection = String.join(", ", java.util.Arrays.stream(columns).map(c -> "`" + c + "`").toList());
        try (PreparedStatement statement = connection.prepareStatement(
                "select id, " + selection + " from `" + table + "`");
             ResultSet result = statement.executeQuery()) {
            while (result.next()) {
                StringBuilder line = new StringBuilder("#" + result.getObject(1));
                for (int index = 0; index < columns.length; index++) {
                    Object value = result.getObject(index + 2);
                    String text = value == null ? "NULL" : String.valueOf(value);
                    if (text.length() > 70) text = text.substring(0, 70) + "…";
                    line.append(" | ").append(columns[index]).append('=').append(text);
                }
                lines.put(String.valueOf(result.getObject(1)), line.toString());
            }
        }
        return new LinkedHashMap<>(lines);
    }
}
.scratch/db-merge/FlywayProbe.java
New file
@@ -0,0 +1,57 @@
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationInfo;
/**
 * Flyway 探针:默认只读(info),带 --migrate 才真的执行迁移。
 *
 * <p>用法:java FlywayProbe &lt;jdbc-url&gt; [--migrate] [--locations=filesystem:...] [--no-validate]
 */
public class FlywayProbe {
    public static void main(String[] args) {
        String url = args[0];
        boolean migrate = false;
        boolean validate = true;
        String locations = "classpath:db/migration";
        boolean outOfOrder = false;
        for (String arg : args) {
            if ("--migrate".equals(arg)) migrate = true;
            if ("--no-validate".equals(arg)) validate = false;
            if ("--out-of-order".equals(arg)) outOfOrder = true;
            if (arg.startsWith("--locations=")) locations = arg.substring("--locations=".length());
        }
        Flyway flyway = Flyway.configure()
                .dataSource(url, "root", "123456")
                .locations(locations)
                .baselineOnMigrate(true)
                .validateOnMigrate(validate)
                .outOfOrder(outOfOrder)
                .load();
        System.out.println("locations=" + locations + " validateOnMigrate=" + validate
                + " outOfOrder=" + outOfOrder);
        System.out.println("== CURRENT ==");
        MigrationInfo current = flyway.info().current();
        System.out.println("  已应用到: " + (current == null ? "(空)" : current.getVersion() + " " + current.getDescription()));
        System.out.println("== PENDING ==");
        MigrationInfo[] pending = flyway.info().pending();
        for (MigrationInfo info : pending) {
            System.out.println("  " + info.getVersion() + " | " + info.getDescription() + " | " + info.getType());
        }
        System.out.println("  共 " + pending.length + " 条待应用");
        if (!migrate) {
            System.out.println("(只读模式,未执行迁移)");
            return;
        }
        System.out.println("== MIGRATE ==");
        try {
            var result = flyway.migrate();
            System.out.println("  应用了 " + result.migrationsExecuted + " 条,目标版本 "
                    + result.targetSchemaVersion + ",初始版本 " + result.initialSchemaVersion);
        } catch (Exception exception) {
            System.out.println("  !! 迁移失败:" + exception.getMessage());
        }
    }
}
.scratch/db-merge/Query.java
New file
@@ -0,0 +1,33 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
/**
 * 只读探针:执行一条查询并把结果按列打印出来(用于核对服务器上的数据现状)。
 *
 * <p>用法:java Query &lt;jdbc-url&gt; "select ..."
 */
public class Query {
    public static void main(String[] args) throws Exception {
        try (Connection connection = DriverManager.getConnection(args[0], "root", "123456");
             Statement statement = connection.createStatement();
             ResultSet rows = statement.executeQuery(args[1])) {
            ResultSetMetaData meta = rows.getMetaData();
            int columns = meta.getColumnCount();
            StringBuilder header = new StringBuilder();
            for (int index = 1; index <= columns; index++) header.append(meta.getColumnLabel(index)).append(" | ");
            System.out.println(header);
            while (rows.next()) {
                StringBuilder line = new StringBuilder();
                for (int index = 1; index <= columns; index++) {
                    Object value = rows.getObject(index);
                    line.append(value == null ? "NULL" : String.valueOf(value)).append(" | ");
                }
                System.out.println(line);
            }
        }
    }
}
.scratch/db-merge/README.md
New file
@@ -0,0 +1,80 @@
# 两个数据库的合并:做法与工具
本目录记录 2026-09-16 把**本机开发库**与**服务器库**并到一起的过程,以及可复现的工具与判定规则。
目录里的探针都是一次性只读/迁移工具,不是业务代码。
## 当时的事实
| | 服务器库 `192.168.110.13:3306/talent_card` | 本机开发库 `127.0.0.1:8134/talent_card` |
| --- | --- | --- |
| MySQL | 5.7.44 | 8.0.36 |
| 表数 | 123(95 张迁移产物 + 28 张历史遗留表,后者代码已不引用) | 95(与迁移产物完全一致) |
| flyway | 启用,历史停在 V111 | **关闭**,`flyway_schema_history` 为空 |
| 结构缺口 | 缺 V112–V147(其中 V112/V113/V115/V119 会与手工列相撞) | 只缺 V147 的 6 列 |
结论:两边缺的都不只是本次改动——服务器库连合并前主线代码要的列(`benefit_order.collect_huifu_id`、
`payment_transaction.funds_confirmation_status` 等)都没有,所以支付/核销/退款/进件在那里本来就跑不起来。
## 工具
| 文件 | 用途 |
| --- | --- |
| `SchemaDigest.java` | 只读:打印一个库的结构摘要(flyway 历史、表、列、索引),用于两库比对 |
| `SchemaDump.java` | 只读:导出全部表的结构(`show create table`)或指定表的数据(INSERT),做备份 |
| `RunSqlFile.java` | 执行一个 .sql 文件,支持 `DELIMITER`(给本机库补单条迁移用) |
| `FlywayProbe.java` | Flyway 只读 `info`(默认)或 `--migrate`;可指定 locations 与 `--no-validate` |
| `RowCompare.java` | 只读:按主键逐行比对两库,报「同内容/内容不同/仅左/仅右」,**只比对共有的列** |
| `DiffRows.java` | 只读:把差异行按「仅左/仅右/同主键两侧并列」打印出来,用于判断是不是同一实体 |
| `SyncTool.java` | 两库按主键做**并集插入**(只插不改),`--dry-run` 可先看清单 |
| `Query.java` | 只读:执行一条查询并打印结果 |
| `restore-history.sql` | 迁移前服务器 `flyway_schema_history` 的快照(还原迁移账本用) |
编译与运行(classpath 复用模块的依赖清单):
```bash
cd talent-card-server && ./mvnw.cmd -o dependency:build-classpath -Dmdep.outputFile=target/cp.txt   # cp.txt 已在仓库外,首次需生成
cd .. && javac -encoding UTF-8 -cp "$(cat talent-card-server/target/cp.txt)" \
    -d talent-card-server/target/probe-classes .scratch/db-merge/*.java
java -cp "talent-card-server/target/probe-classes;$(cat talent-card-server/target/cp.txt)" \
    SchemaDigest "jdbc:mysql://127.0.0.1:8134/talent_card?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false"
```
## 做法(顺序)
1. **只读盘点**:`SchemaDigest` 两台各跑一次对比结构;`RowCompare` 逐行比对;`DiffRows` 看具体差异行。
2. **备份**:`SchemaDump` 导出两库的结构与数据到 `backup/`(**该目录被 .gitignore 挡住**:里面有会话令牌、
   手机号,绝不入库)。
3. **干跑**:先把要应用的迁移复制到 `dryrun-migrations/`,在本机建的「服务器结构副本」上跑一遍
   (`FlywayProbe --migrate --no-validate --locations=filesystem:...`),确认不撞列、不依赖被跳过的迁移:
   ```bash
   mkdir -p .scratch/db-merge/dryrun-migrations
   for v in $(seq 127 147); do cp talent-card-server/src/main/resources/db/migration/V${v}__*.sql .scratch/db-merge/dryrun-migrations/; done
   ```
4. **应用迁移**:服务器用 `FlywayProbe --migrate`(只放 V127–V147 的 locations + `--no-validate`,因为
   V112–V126 仍待应用,全量 locations 会让 V112 撞列失败);本机库 flyway 关着,用 `RunSqlFile` 跑 V147。
5. **数据并集**:`SyncTool` 双向各跑一次(`--dry-run` 先看清单):
   ```bash
   java -cp ... SyncTool <本机url> <服务器url> "--rekey=merchant_operator:10,34,35"
   java -cp ... SyncTool <服务器url> <本机url>
   ```
6. **验收**:`RowCompare` 复核——所有业务表的「仅左/仅右」都应为 0。
## 冲突判定规则(重要)
- **只插不改。** 目标库缺的行按原主键插入;主键存在但内容不同的行一律**不覆盖**,只统计。
- 两库的同一主键**未必是同一实体**(各自自增、各自新增)。`merchant_operator` 就是这样:本机 #10/#34/#35
  是商户 631/632/633 的登录账号,服务器同 id 属于商户 99/90/126。这类**已确认**的撞车才用
  `--rekey=表:主键,...` 换新主键插入,绝不覆盖对方那条。
- **生成列不参与插入**:V132/V134/V135 加了 `generated always as ...`,显式写值会被 MySQL 拒绝。
- **自然唯一键冲突交给数据库拒绝**:被拒的行(如 6 行营业时段、2 行协议类型)说明目标库已有同键的行,
  记录即可,不要绕过去造重复。
- **不替人改密码**:`merchant_operator` 有一行的 `password_hash` 两边不同,保持各侧原值。
## 复用时的注意事项
- **两个库会再次分叉。** 只要两边继续各自被写(各自自增 id),下一次仍然需要逐行判读。要长期保持两边
  可用,应选一个库作为唯一事实源,另一边定期从它刷新,而不是反复双向合并。
- 服务器上 `V112–V126` 仍未应用,且 Flyway 默认 `outOfOrder=false` 会**静默忽略**它们(`info` 显示
  「0 条待应用」)。要补必须显式开 `outOfOrder=true`,且那 4 条撞列的迁移要先处理——属于其他业务域。
- 服务器库结构上比迁移产物多 28 张历史遗留表(`sys_user`、`payment_record`、`coupon` 等),
  已确认合并后的代码不再引用任何一张,属死表。
.scratch/db-merge/RowCompare.java
New file
@@ -0,0 +1,146 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
 * 只读探针:按主键逐行比对两个库的同名表,回答「同一主键在两边是不是同一份内容」。
 *
 * <p>用法:java RowCompare &lt;左库url&gt; &lt;右库url&gt; [表名,表名...]
 * 默认比对内置的核心业务表清单。不做任何写入。
 */
public class RowCompare {
    private static final String[] DEFAULT_TABLES = {
            "merchant", "merchant_store", "merchant_product", "merchant_operator",
            "merchant_onboarding_application", "merchant_onboarding_progress", "merchant_bank_card",
            "merchant_verification_attempt", "merchant_agreement_acceptance",
            "benefit_order", "verify_record", "payment_transaction", "order_refund",
            "payment_reconciliation_issue", "payment_callback_event",
            "talent_user", "talent_card", "talent_dynamic_code",
            "admin_user", "merchant_coupon", "news_article",
    };
    public static void main(String[] args) throws Exception {
        try (Connection left = DriverManager.getConnection(args[0], "root", "123456");
             Connection right = DriverManager.getConnection(args[1], "root", "123456")) {
            String[] tables = args.length > 2 ? args[2].split(",") : DEFAULT_TABLES;
            System.out.printf("%-34s %7s %7s %7s %7s %7s %7s%n",
                    "表", "总数L", "总数R", "同内容", "内容不同", "仅左", "仅右");
            for (String table : tables) {
                compare(left, right, table.trim());
            }
        }
    }
    private static void compare(Connection left, Connection right, String table) {
        try {
            List<String> keys = primaryKey(left, table);
            if (keys.isEmpty()) {
                System.out.printf("%-34s %7s%n", table, "(无单列主键,跳过)");
                return;
            }
            String key = keys.get(0);
            // 只比对两边都有的列:本机库可能还没应用某些迁移,列集合不同不代表行内容不同。
            List<String> leftColumns = columns(left, table);
            List<String> rightColumns = columns(right, table);
            List<String> shared = new ArrayList<>(leftColumns);
            shared.retainAll(rightColumns);
            if (!shared.contains(key)) {
                System.out.printf("%-34s %7s%n", table, "(主键列不在共有列里)");
                return;
            }
            Map<String, List<String>> leftRows = fingerprint(left, table, key, shared);
            Map<String, List<String>> rightRows = fingerprint(right, table, key, shared);
            int same = 0;
            int different = 0;
            List<String> conflicts = new ArrayList<>();
            List<String> example = null;
            for (Map.Entry<String, List<String>> entry : leftRows.entrySet()) {
                List<String> other = rightRows.get(entry.getKey());
                if (other == null) continue;
                if (other.equals(entry.getValue())) same++;
                else {
                    different++;
                    if (conflicts.size() < 8) conflicts.add(entry.getKey());
                    if (example == null) example = differingColumns(shared, entry.getValue(), other);
                }
            }
            int onlyLeft = 0;
            for (String id : leftRows.keySet()) if (!rightRows.containsKey(id)) onlyLeft++;
            int onlyRight = 0;
            for (String id : rightRows.keySet()) if (!leftRows.containsKey(id)) onlyRight++;
            System.out.printf("%-34s %7d %7d %7d %7d %7d %7d%n",
                    table, leftRows.size(), rightRows.size(), same, different, onlyLeft, onlyRight);
            if (!conflicts.isEmpty()) {
                System.out.println("      主键相同但内容不同:" + String.join(", ", conflicts)
                        + (different > conflicts.size() ? " ...(共 " + different + " 条)" : ""));
                if (example != null) System.out.println("      差异列(示例):" + String.join(", ", example));
            }
        } catch (Exception exception) {
            System.out.printf("%-34s %7s%n", table, "(" + exception.getMessage() + ")");
        }
    }
    private static List<String> differingColumns(List<String> columns, List<String> left, List<String> right) {
        List<String> differing = new ArrayList<>();
        for (int index = 0; index < columns.size(); index++) {
            if (!left.get(index).equals(right.get(index))) differing.add(columns.get(index));
        }
        return differing;
    }
    private static List<String> columns(Connection connection, String table) throws Exception {
        List<String> columns = new ArrayList<>();
        try (PreparedStatement statement = connection.prepareStatement("""
                select column_name from information_schema.columns
                where table_schema = database() and table_name = ? order by ordinal_position
                """)) {
            statement.setString(1, table);
            try (ResultSet rows = statement.executeQuery()) {
                while (rows.next()) columns.add(rows.getString(1));
            }
        }
        return columns;
    }
    private static List<String> primaryKey(Connection connection, String table) throws Exception {
        List<String> keys = new ArrayList<>();
        try (PreparedStatement statement = connection.prepareStatement("""
                select column_name from information_schema.key_column_usage
                where table_schema = database() and table_name = ? and constraint_name = 'PRIMARY'
                order by ordinal_position
                """)) {
            statement.setString(1, table);
            try (ResultSet rows = statement.executeQuery()) {
                while (rows.next()) keys.add(rows.getString(1));
            }
        }
        return keys;
    }
    /** 主键 -> 共有列的内容指纹(按列顺序拼串,null 用特殊标记)。 */
    private static Map<String, List<String>> fingerprint(Connection connection, String table, String key,
                                                         List<String> columns) throws Exception {
        Map<String, List<String>> rows = new TreeMap<>();
        String selection = String.join(", ", columns.stream().map(column -> "`" + column + "`").toList());
        try (PreparedStatement statement = connection.prepareStatement(
                "select " + selection + " from `" + table + "`");
             ResultSet result = statement.executeQuery()) {
            while (result.next()) {
                List<String> content = new ArrayList<>(columns.size());
                for (int index = 1; index <= columns.size(); index++) {
                    Object value = result.getObject(index);
                    content.add(value == null ? "\u0000" : String.valueOf(value));
                }
                rows.put(String.valueOf(result.getObject(key)), content);
            }
        }
        return rows;
    }
}
.scratch/db-merge/RunSqlFile.java
New file
@@ -0,0 +1,63 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
/**
 * 把一个 .sql 文件按语句逐条执行(备份文件与一次性 DDL 用)。
 *
 * <p>不处理 DELIMITER:Flyway 自己的迁移文件由 Flyway 执行,这里只跑不含存储过程的脚本。
 * 用法:java RunSqlFile &lt;jdbc-url&gt; &lt;file.sql&gt;
 */
public class RunSqlFile {
    public static void main(String[] args) throws Exception {
        String url = args[0];
        Path file = Path.of(args[1]);
        List<String> statements = split(Files.readString(file, StandardCharsets.UTF_8));
        int failed = 0;
        try (Connection connection = DriverManager.getConnection(url, "root", "123456")) {
            try (Statement statement = connection.createStatement()) {
                for (String sql : statements) {
                    try {
                        statement.execute(sql);
                    } catch (Exception exception) {
                        failed++;
                        String head = sql.length() > 120 ? sql.substring(0, 120) + "..." : sql;
                        System.out.println("!! 失败:" + exception.getMessage());
                        System.out.println("   " + head.replace('\n', ' '));
                    }
                }
            }
        }
        System.out.println("执行语句 " + statements.size() + " 条,失败 " + failed + " 条");
    }
    /** 按行尾分号切分,并支持 {@code DELIMITER} 指令(迁移里的存储过程需要)。跳过空语句与纯注释块。 */
    private static List<String> split(String content) {
        List<String> statements = new ArrayList<>();
        StringBuilder current = new StringBuilder();
        String delimiter = ";";
        for (String line : content.split("\n")) {
            String trimmed = line.trim();
            String upper = trimmed.toUpperCase(java.util.Locale.ROOT);
            if (upper.startsWith("DELIMITER ")) {
                delimiter = trimmed.substring("DELIMITER ".length()).trim();
                continue;
            }
            if (trimmed.startsWith("--") && current.toString().isBlank()) continue;
            current.append(line).append('\n');
            if (trimmed.endsWith(delimiter)) {
                String sql = current.toString().trim();
                String withoutTrailing = sql.substring(0, sql.length() - delimiter.length()).trim();
                if (!withoutTrailing.isBlank() && !withoutTrailing.startsWith("--")) statements.add(withoutTrailing);
                current.setLength(0);
            }
        }
        return statements;
    }
}
.scratch/db-merge/SchemaDigest.java
New file
@@ -0,0 +1,86 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
 * 只读探针:把一个库的结构摘要打到标准输出,用于合并前比对目标库与本机开发库。
 *
 * <p>不含任何写入语句。用法:java SchemaDigest &lt;jdbc-url&gt;
 */
public class SchemaDigest {
    public static void main(String[] args) throws Exception {
        String url = args[0];
        try (Connection connection = DriverManager.getConnection(url, "root", "123456")) {
            String database = connection.getCatalog();
            System.out.println("== SERVER ==");
            try (Statement statement = connection.createStatement();
                 ResultSet rows = statement.executeQuery("select version()")) {
                rows.next();
                System.out.println("  " + rows.getString(1));
            }
            System.out.println("== DATABASE " + database + " ==");
            System.out.println("== FLYWAY ==");
            try (Statement statement = connection.createStatement();
                 ResultSet rows = statement.executeQuery(
                         "select version, description, success from flyway_schema_history order by installed_rank")) {
                while (rows.next()) {
                    System.out.println("  " + rows.getString(1) + " | " + rows.getString(2)
                            + " | success=" + rows.getBoolean(3));
                }
            } catch (SQLException exception) {
                System.out.println("  (没有 flyway_schema_history:" + exception.getMessage() + ")");
            }
            System.out.println("== TABLES ==");
            try (PreparedStatement statement = connection.prepareStatement("""
                    select table_name, table_rows, table_comment from information_schema.tables
                    where table_schema = ? order by table_name
                    """)) {
                statement.setString(1, database);
                try (ResultSet rows = statement.executeQuery()) {
                    while (rows.next()) {
                        System.out.println("  " + rows.getString(1) + " rows~" + rows.getLong(2)
                                + " [" + rows.getString(3) + "]");
                    }
                }
            }
            System.out.println("== COLUMNS ==");
            try (PreparedStatement statement = connection.prepareStatement("""
                    select table_name, column_name, column_type, is_nullable, coalesce(column_default, '')
                    from information_schema.columns where table_schema = ?
                    order by table_name, ordinal_position
                    """)) {
                statement.setString(1, database);
                try (ResultSet rows = statement.executeQuery()) {
                    while (rows.next()) {
                        System.out.println("  " + rows.getString(1) + "." + rows.getString(2)
                                + " " + rows.getString(3) + " null=" + rows.getString(4)
                                + " default=" + rows.getString(5));
                    }
                }
            }
            System.out.println("== INDEXES ==");
            try (PreparedStatement statement = connection.prepareStatement("""
                    select table_name, index_name, non_unique, seq_in_index, column_name
                    from information_schema.statistics where table_schema = ?
                    order by table_name, index_name, seq_in_index
                    """)) {
                statement.setString(1, database);
                try (ResultSet rows = statement.executeQuery()) {
                    while (rows.next()) {
                        System.out.println("  " + rows.getString(1) + " " + rows.getString(2)
                                + " unique=" + (rows.getInt(3) == 0) + " seq=" + rows.getInt(4)
                                + " col=" + rows.getString(5));
                    }
                }
            }
        }
    }
}
.scratch/db-merge/SchemaDump.java
New file
@@ -0,0 +1,119 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
/**
 * 只读探针:导出某个库的结构(SHOW CREATE TABLE)或指定表的数据(INSERT),用于迁移前备份。
 *
 * <p>用法:
 * <pre>
 *   java SchemaDump &lt;jdbc-url&gt;                      # 全部表的结构
 *   java SchemaDump &lt;jdbc-url&gt; --data=t1,t2         # 指定表的数据
 * </pre>
 */
public class SchemaDump {
    public static void main(String[] args) throws Exception {
        String url = args[0];
        String dataTables = null;
        for (String arg : args) {
            if (arg.startsWith("--data=")) dataTables = arg.substring("--data=".length());
        }
        try (Connection connection = DriverManager.getConnection(url, "root", "123456")) {
            if (dataTables == null) {
                dumpSchema(connection);
            } else {
                for (String table : dataTables.split(",")) {
                    dumpData(connection, table.trim());
                }
            }
        }
    }
    private static void dumpSchema(Connection connection) throws Exception {
        System.out.println("-- 结构备份:" + connection.getCatalog() + ",导出时间 " + java.time.LocalDateTime.now());
        System.out.println("-- 还原方式:在空库里按顺序执行本文件(表按名称排序,先建表后加外键由 MySQL 自行处理)。");
        System.out.println("SET FOREIGN_KEY_CHECKS = 0;");
        for (String table : tables(connection)) {
            try (Statement statement = connection.createStatement();
                 ResultSet rows = statement.executeQuery("show create table `" + table + "`")) {
                rows.next();
                System.out.println();
                System.out.println("-- " + table);
                System.out.println("DROP TABLE IF EXISTS `" + table + "`;");
                System.out.println(rows.getString(2) + ";");
            }
        }
        System.out.println();
        System.out.println("SET FOREIGN_KEY_CHECKS = 1;");
    }
    private static void dumpData(Connection connection, String table) throws Exception {
        System.out.println();
        System.out.println("-- 数据备份 " + table + "(导出时间 " + java.time.LocalDateTime.now() + ")");
        try (Statement statement = connection.createStatement();
             ResultSet rows = statement.executeQuery("select * from `" + table + "`")) {
            int columns = rows.getMetaData().getColumnCount();
            List<String> names = new ArrayList<>();
            for (int index = 1; index <= columns; index++) names.add(rows.getMetaData().getColumnLabel(index));
            int count = 0;
            while (rows.next()) {
                StringJoiner values = new StringJoiner(", ");
                for (int index = 1; index <= columns; index++) {
                    values.add(literal(rows.getObject(index)));
                }
                System.out.println("INSERT INTO `" + table + "` (`" + String.join("`, `", names) + "`) VALUES ("
                        + values + ");");
                count++;
            }
            System.out.println("-- " + table + " 共 " + count + " 行");
        }
    }
    private static List<String> tables(Connection connection) throws Exception {
        List<String> tables = new ArrayList<>();
        try (PreparedStatement statement = connection.prepareStatement("""
                select table_name from information_schema.tables
                where table_schema = ? and table_type = 'BASE TABLE' order by table_name
                """)) {
            statement.setString(1, connection.getCatalog());
            try (ResultSet rows = statement.executeQuery()) {
                while (rows.next()) tables.add(rows.getString(1));
            }
        }
        return tables;
    }
    /** Java 17 已经移除 javax.xml.bind,自带一个十六进制编码。 */
    private static String hex(byte[] bytes) {
        StringBuilder text = new StringBuilder(bytes.length * 2);
        for (byte value : bytes) text.append(String.format("%02X", value));
        return text.toString();
    }
    /** 一个够用的字面量转义:NULL、数字裸写、其余加引号并转义控制字符。 */
    private static String literal(Object value) {
        if (value == null) return "NULL";
        if (value instanceof Number || value instanceof Boolean) return String.valueOf(value);
        if (value instanceof byte[] bytes) return "0x" + hex(bytes);
        String text = String.valueOf(value);
        StringBuilder escaped = new StringBuilder("'");
        for (char character : text.toCharArray()) {
            switch (character) {
                case '\'' -> escaped.append("''");
                case '\\' -> escaped.append("\\\\");
                case '\n' -> escaped.append("\\n");
                case '\r' -> escaped.append("\\r");
                case '\0' -> escaped.append("\\0");
                case '\u001a' -> escaped.append("\\Z");
                default -> escaped.append(character);
            }
        }
        return escaped.append("'").toString();
    }
}
.scratch/db-merge/SyncTool.java
New file
@@ -0,0 +1,235 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Types;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
 * 两个库按主键做并集:目标库缺的行插入,同主键内容不同的行按「较新者胜」更新。
 *
 * <p>用法:java SyncTool &lt;源url&gt; &lt;目标url&gt; [--dry-run]
 *
 * <p>只处理两边都存在的业务表;会话、审计、动态码、事件这类瞬时表与仅存在于一侧的遗留表都不动。
 * 全部使用参数化语句,不拼字符串,避免转义问题。
 */
public class SyncTool {
    /** 不参与合并的表:瞬时状态、审计流水、固定参考数据与迁移账本。 */
    private static final List<String> EXCLUDED_SUFFIXES = List.of(
            "_session", "_audit", "_event", "_log", "_dynamic_code", "_verification_code",
            "bank_branch_directory", "flyway_schema_history", "merchant_verification_attempt",
            "merchant_login_audit", "admin_login_audit", "talent_dynamic_code",
            "order_dynamic_verification_code", "free_stay_verification_code");
    public static void main(String[] args) throws Exception {
        String sourceUrl = args[0];
        String targetUrl = args[1];
        boolean dryRun = false;
        java.util.Map<String, java.util.Set<String>> rekey = new java.util.HashMap<>();
        for (String arg : args) {
            if ("--dry-run".equals(arg)) dryRun = true;
            // --rekey=表:主键,主键 —— 只对这些「已确认同主键不同实体」的行换新主键插入。
            if (arg.startsWith("--rekey=")) {
                String[] parts = arg.substring("--rekey=".length()).split(":");
                java.util.Set<String> ids = new java.util.HashSet<>();
                for (String id : parts[1].split(",")) ids.add(id.trim());
                rekey.put(parts[0].trim(), ids);
            }
        }
        try (Connection source = DriverManager.getConnection(sourceUrl, "root", "123456");
             Connection target = DriverManager.getConnection(targetUrl, "root", "123456")) {
            List<String> tables = sharedTables(source, target);
            int totalInserted = 0;
            int totalConflicts = 0;
            int totalSkipped = 0;
            System.out.printf("%-38s %8s %9s %8s%n", "表", "插入", "冲突未动", "插入失败");
            for (String table : tables) {
                if (excluded(table)) continue;
                String key = primaryKey(source, table);
                if (key == null) continue;
                List<String> columns = sharedColumns(source, target, table);
                if (columns == null || columns.isEmpty() || !columns.contains(key)) continue;
                int[] result = sync(source, target, table, key, columns, dryRun, rekey.get(table));
                if (result[0] != 0 || result[1] != 0 || result[2] != 0) {
                    System.out.printf("%-38s %8d %9d %8d%n", table, result[0], result[1], result[2]);
                }
                totalInserted += result[0];
                totalConflicts += result[1];
                totalSkipped += result[2];
            }
            System.out.println("合计:插入 " + totalInserted + " 行,冲突未动 " + totalConflicts
                    + " 行,插入失败 " + totalSkipped + " 行" + (dryRun ? "(干跑,未写入)" : "(已写入)"));
        }
    }
    /**
     * 只做「并集插入」:目标库缺的行按原主键插入;主键存在但内容不同的行一律不覆盖(只计数)。
     *
     * <p>冲突行不动是有意的:两个库的同一主键未必是同一实体(各自的登录账号、卡、进件记录会各自增长),
     * 覆盖任何一侧都会改坏那侧的数据。{@code rekey} 列表里的表是已确认「同主键不同实体」的,
     * 这类行改用目标库的新主键插入,避免丢掉源侧那条真实存在的数据。
     */
    private static int[] sync(Connection source, Connection target, String table, String key,
                              List<String> columns, boolean dryRun, java.util.Set<String> rekeyIds) throws Exception {
        Map<String, Object[]> sourceRows = rows(source, table, key, columns);
        Map<String, Object[]> targetRows = rows(target, table, key, columns);
        String columnList = String.join(", ", columns.stream().map(c -> "`" + c + "`").toList());
        String placeholders = String.join(", ", columns.stream().map(c -> "?").toList());
        int keyIndex = columns.indexOf(key);
        long nextId = -1;
        int inserted = 0;
        int conflicts = 0;
        int failed = 0;
        for (Map.Entry<String, Object[]> entry : sourceRows.entrySet()) {
            Object[] targetRow = targetRows.get(entry.getKey());
            Object[] values = entry.getValue();
            String insertId = entry.getKey();
            if (targetRow != null) {
                if (sameRow(values, targetRow)) continue;
                if (rekeyIds == null || !rekeyIds.contains(entry.getKey())) {
                    conflicts++;
                    continue;
                }
                // 同主键不同实体:换一个新主键插入,别覆盖目标库那条。只在真要换号时才取 max,
                // 免得在字符串主键(例如 talent_user_agreement)上做数字解析。
                if (nextId < 0) nextId = maxId(target, table, key) + 1;
                insertId = String.valueOf(nextId);
                values = values.clone();
                values[keyIndex] = nextId;
                nextId++;
            }
            if (dryRun) {
                inserted++;
                continue;
            }
            try (PreparedStatement statement = target.prepareStatement(
                    "insert into `" + table + "` (" + columnList + ") values (" + placeholders + ")")) {
                bind(statement, values, columns);
                statement.executeUpdate();
                inserted++;
            } catch (Exception exception) {
                failed++;
                System.out.println("      ! " + table + " 主键 " + insertId + " 插入失败:" + exception.getMessage());
            }
        }
        return new int[]{inserted, conflicts, failed};
    }
    private static long maxId(Connection connection, String table, String key) throws Exception {
        try (PreparedStatement statement = connection.prepareStatement(
                "select coalesce(max(`" + key + "`), 0) from `" + table + "`");
             ResultSet rows = statement.executeQuery()) {
            rows.next();
            return rows.getLong(1);
        }
    }
    private static boolean sameRow(Object[] source, Object[] target) {
        if (source.length != target.length) return false;
        for (int index = 0; index < source.length; index++) {
            String left = source[index] == null ? "\u0000" : String.valueOf(source[index]);
            String right = target[index] == null ? "\u0000" : String.valueOf(target[index]);
            if (!left.equals(right)) return false;
        }
        return true;
    }
    private static Map<String, Object[]> rows(Connection connection, String table, String key,
                                              List<String> columns) throws Exception {
        Map<String, Object[]> rows = new TreeMap<>();
        String selection = String.join(", ", columns.stream().map(c -> "`" + c + "`").toList());
        try (PreparedStatement statement = connection.prepareStatement(
                "select " + selection + " from `" + table + "`");
             ResultSet result = statement.executeQuery()) {
            while (result.next()) {
                Object[] values = new Object[columns.size()];
                for (int index = 0; index < columns.size(); index++) values[index] = result.getObject(index + 1);
                rows.put(String.valueOf(result.getObject(key)), values);
            }
        }
        return rows;
    }
    private static void bind(PreparedStatement statement, Object[] values, List<String> columns) throws Exception {
        for (int index = 0; index < values.length; index++) {
            if (values[index] == null) statement.setNull(index + 1, Types.NULL);
            else statement.setObject(index + 1, values[index]);
        }
    }
    private static boolean excluded(String table) {
        for (String suffix : EXCLUDED_SUFFIXES) if (table.equals(suffix) || table.endsWith(suffix)) return true;
        return false;
    }
    private static List<String> sharedTables(Connection source, Connection target) throws Exception {
        List<String> sourceTables = tables(source);
        List<String> targetTables = tables(target);
        sourceTables.retainAll(targetTables);
        return sourceTables;
    }
    private static List<String> tables(Connection connection) throws Exception {
        List<String> tables = new ArrayList<>();
        try (PreparedStatement statement = connection.prepareStatement("""
                select table_name from information_schema.tables
                where table_schema = database() and table_type = 'BASE TABLE' order by table_name
                """);
             ResultSet rows = statement.executeQuery()) {
            while (rows.next()) tables.add(rows.getString(1));
        }
        return tables;
    }
    private static List<String> sharedColumns(Connection source, Connection target, String table) throws Exception {
        List<String> columns = columns(source, table);
        columns.retainAll(columns(target, table));
        return columns;
    }
    /**
     * 可写列:跳过生成列({@code generated always as ...})。生成列由数据库按表达式算出来,
     * 显式写入会被 MySQL 拒绝(V132/V134/V135 都加了生成列)。
     */
    private static List<String> columns(Connection connection, String table) throws Exception {
        List<String> columns = new ArrayList<>();
        try (PreparedStatement statement = connection.prepareStatement("""
                select column_name, extra, generation_expression from information_schema.columns
                where table_schema = database() and table_name = ? order by ordinal_position
                """)) {
            statement.setString(1, table);
            try (ResultSet rows = statement.executeQuery()) {
                while (rows.next()) {
                    String extra = rows.getString(2) == null ? "" : rows.getString(2).toUpperCase(java.util.Locale.ROOT);
                    String expression = rows.getString(3);
                    boolean generated = extra.contains("GENERATED")
                            || (expression != null && !expression.isBlank());
                    if (!generated) columns.add(rows.getString(1));
                }
            }
        }
        return columns;
    }
    private static String primaryKey(Connection connection, String table) throws Exception {
        List<String> keys = new ArrayList<>();
        try (PreparedStatement statement = connection.prepareStatement("""
                select column_name from information_schema.key_column_usage
                where table_schema = database() and table_name = ? and constraint_name = 'PRIMARY'
                order by ordinal_position
                """)) {
            statement.setString(1, table);
            try (ResultSet rows = statement.executeQuery()) {
                while (rows.next()) keys.add(rows.getString(1));
            }
        }
        return keys.size() == 1 ? keys.get(0) : null;
    }
}
.scratch/db-merge/restore-history.sql
New file
@@ -0,0 +1,110 @@
delete from flyway_schema_history;
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (1, '1', '<< Flyway Baseline >>', 'BASELINE', '<< Flyway Baseline >>', NULL, 'root', '2026-07-17 15:11:02.0', 0, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (2, '2', 'base business tables', 'SQL', 'V2__base_business_tables.sql', -2043420698, 'root', '2026-07-17 15:34:42.0', 51, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (3, '3', 'merchant admin fields', 'SQL', 'V3__merchant_admin_fields.sql', 207505230, 'root', '2026-07-20 11:21:55.0', 34, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (4, '4', 'merchant coupon tables', 'SQL', 'V4__merchant_coupon_tables.sql', 1321273322, 'root', '2026-07-23 14:17:45.0', 32, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (5, '5', 'merchant product form config', 'SQL', 'V5__merchant_product_form_config.sql', -1898050533, 'root', '2026-07-23 15:04:21.0', 144, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (6, '6', 'order refund records', 'SQL', 'V6__order_refund_records.sql', -1905488592, 'root', '2026-07-23 17:18:47.0', 24, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (7, '7', 'service applications', 'SQL', 'V7__service_applications.sql', -862055421, 'root', '2026-07-23 17:49:47.0', 36, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (8, '8', 'card applications', 'SQL', 'V8__card_applications.sql', 1949570785, 'root', '2026-07-24 11:26:02.0', 41, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (9, '9', 'operation management', 'SQL', 'V9__operation_management.sql', 553221548, 'root', '2026-07-24 15:31:18.0', 41, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (10, '10', 'news articles', 'SQL', 'V10__news_articles.sql', -1923086938, 'root', '2026-07-24 15:31:18.0', 10, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (11, '11', 'seed demo news articles', 'SQL', 'V11__seed_demo_news_articles.sql', 1961096501, 'root', '2026-07-24 15:48:12.0', 10, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (12, '12', 'seed demo merchants and products', 'SQL', 'V12__seed_demo_merchants_and_products.sql', -1650493658, 'root', '2026-07-24 16:21:40.0', 19, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (13, '13', 'seed demo hotpot product', 'SQL', 'V13__seed_demo_hotpot_product.sql', -1195203486, 'root', '2026-07-24 16:24:21.0', 7, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (14, '14', 'backfill demo product images', 'SQL', 'V14__backfill_demo_product_images.sql', -1005819135, 'root', '2026-07-24 18:04:19.0', 13, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (15, '15', 'food topic configuration', 'SQL', 'V15__food_topic_configuration.sql', 31999599, 'root', '2026-07-27 14:13:18.0', 74, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (16, '16', 'merchant detail configuration', 'SQL', 'V16__merchant_detail_configuration.sql', -2004483606, 'root', '2026-07-27 15:40:03.0', 262, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (17, '17', 'seed default food merchant product', 'SQL', 'V17__seed_default_food_merchant_product.sql', 687180813, 'root', '2026-07-27 15:41:42.0', 13, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (18, '18', 'backfill default food product detail', 'SQL', 'V18__backfill_default_food_product_detail.sql', 1212055854, 'root', '2026-07-27 16:22:45.0', 5, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (19, '19', 'seed default food coupon', 'SQL', 'V19__seed_default_food_coupon.sql', 513640743, 'root', '2026-07-27 17:04:06.0', 10, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (20, '20', 'talent card dynamic codes', 'SQL', 'V20__talent_card_dynamic_codes.sql', 831760405, 'root', '2026-07-28 10:40:19.0', 26, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (21, '21', 'seed talent policies', 'SQL', 'V21__seed_talent_policies.sql', -552329513, 'root', '2026-07-28 11:33:32.0', 13, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (22, '22', 'policy detail content configuration', 'SQL', 'V22__policy_detail_content_configuration.sql', 1737648695, 'root', '2026-07-28 14:02:34.0', 136, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (23, '23', 'card upgrade honors', 'SQL', 'V23__card_upgrade_honors.sql', 1211789682, 'root', '2026-07-28 14:35:11.0', 113, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (24, '24', 'seed usage records', 'SQL', 'V24__seed_usage_records.sql', 418900967, 'root', '2026-07-28 15:09:09.0', 10, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (25, '25', 'pending order snapshots', 'SQL', 'V25__pending_order_snapshots.sql', -2138610660, 'root', '2026-07-28 15:19:48.0', 786, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (26, '26', 'align pending order coupon product', 'SQL', 'V26__align_pending_order_coupon_product.sql', -2125754310, 'root', '2026-07-28 15:22:40.0', 8, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (27, '27', 'order dynamic verification codes', 'SQL', 'V27__order_dynamic_verification_codes.sql', -838067477, 'root', '2026-07-28 15:32:02.0', 17, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (28, '28', 'order product cover snapshots', 'SQL', 'V28__order_product_cover_snapshots.sql', -918944819, 'root', '2026-07-28 15:37:40.0', 65, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (29, '29', 'service application processing snapshots', 'SQL', 'V29__service_application_processing_snapshots.sql', -574763565, 'root', '2026-07-28 15:58:04.0', 275, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (30, '30', 'enable demo family cards', 'SQL', 'V30__enable_demo_family_cards.sql', 8653234, 'root', '2026-07-28 16:16:24.0', 6, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (31, '31', 'expand demo family card limit', 'SQL', 'V31__expand_demo_family_card_limit.sql', 1776100093, 'root', '2026-07-28 16:17:47.0', 7, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (32, '32', 'align demo family card level', 'SQL', 'V32__align_demo_family_card_level.sql', -280029008, 'root', '2026-07-28 16:56:41.0', 5, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (33, '33', 'agreement and session revocation', 'SQL', 'V33__agreement_and_session_revocation.sql', 1950064719, 'root', '2026-07-28 17:05:57.0', 32, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (34, '34', 'agreement valid until', 'SQL', 'V34__agreement_valid_until.sql', -1431323798, 'root', '2026-07-28 17:09:09.0', 74, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (35, '35', 'travel topic configuration', 'SQL', 'V35__travel_topic_configuration.sql', 959061645, 'root', '2026-07-28 17:21:18.0', 14, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (36, '36', 'expand demo travel merchants', 'SQL', 'V36__expand_demo_travel_merchants.sql', -1960951959, 'root', '2026-07-28 17:22:55.0', 8, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (37, '37', 'stay topic configuration', 'SQL', 'V37__stay_topic_configuration.sql', 1865939486, 'root', '2026-07-28 17:30:39.0', 19, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (38, '38', 'entertainment topic configuration', 'SQL', 'V38__entertainment_topic_configuration.sql', -2111904807, 'root', '2026-07-28 17:55:43.0', 15, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (39, '39', 'cinema coupon claims', 'SQL', 'V39__cinema_coupon_claims.sql', 1400383867, 'root', '2026-07-28 18:05:00.0', 40, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (40, '40', 'cinema coupon exchange codes', 'SQL', 'V40__cinema_coupon_exchange_codes.sql', -1179843036, 'root', '2026-07-28 18:15:47.0', 304, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (41, '41', 'backfill legacy movie ticket claim', 'SQL', 'V41__backfill_legacy_movie_ticket_claim.sql', 794314898, 'root', '2026-07-28 18:24:42.0', 9, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (42, '42', 'transport topic configuration', 'SQL', 'V42__transport_topic_configuration.sql', -529838104, 'root', '2026-07-29 10:05:30.0', 21, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (43, '43', 'fuel merchant detail configuration', 'SQL', 'V43__fuel_merchant_detail_configuration.sql', -193373451, 'root', '2026-07-29 11:12:27.0', 74, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (44, '44', 'parking merchant detail configuration', 'SQL', 'V44__parking_merchant_detail_configuration.sql', 153231323, 'root', '2026-07-29 11:50:21.0', 55, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (45, '45', 'car wash merchant detail configuration', 'SQL', 'V45__car_wash_merchant_detail_configuration.sql', 1243986746, 'root', '2026-07-29 15:43:09.0', 42, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (46, '46', 'shopping topic configuration', 'SQL', 'V46__shopping_topic_configuration.sql', -405757661, 'root', '2026-07-29 15:56:19.0', 58, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (47, '47', 'shopping service detail configuration', 'SQL', 'V47__shopping_service_detail_configuration.sql', 583084764, 'root', '2026-07-29 16:03:00.0', 20, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (48, '48', 'premium topic configuration', 'SQL', 'V48__premium_topic_configuration.sql', 1808187478, 'root', '2026-07-29 16:10:21.0', 58, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (49, '49', 'premium service coordinates', 'SQL', 'V49__premium_service_coordinates.sql', -856027090, 'root', '2026-07-29 16:11:38.0', 7, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (50, '50', 'spouse employment benefit configuration', 'SQL', 'V50__spouse_employment_benefit_configuration.sql', 100496994, 'root', '2026-07-29 16:20:51.0', 142, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (51, '51', 'expand spouse sensitive fields', 'SQL', 'V51__expand_spouse_sensitive_fields.sql', 371101169, 'root', '2026-07-29 16:26:13.0', 58, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (52, '52', 'medical package configuration', 'SQL', 'V52__medical_package_configuration.sql', 1445312728, 'root', '2026-07-29 16:37:30.0', 34, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (53, '53', 'admin approval benefit configuration', 'SQL', 'V53__admin_approval_benefit_configuration.sql', -501806046, 'root', '2026-07-29 17:00:57.0', 43, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (54, '54', 'finance service detail configuration', 'SQL', 'V54__finance_service_detail_configuration.sql', -1203626545, 'root', '2026-07-29 17:09:10.0', 16, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (55, '55', 'children entrustment benefit configuration', 'SQL', 'V55__children_entrustment_benefit_configuration.sql', 562821011, 'root', '2026-07-29 17:21:44.0', 32, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (56, '56', 'children information', 'SQL', 'V56__children_information.sql', -606900830, 'root', '2026-07-29 17:29:47.0', 20, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (57, '57', 'talent dynamic code context', 'SQL', 'V57__talent_dynamic_code_context.sql', 252711807, 'root', '2026-07-29 17:41:47.0', 181, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (58, '58', 'merchant operator authentication', 'SQL', 'V58__merchant_operator_authentication.sql', 1749070877, 'root', '2026-07-29 20:34:00.0', 104, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (59, '59', 'merchant dashboard and verification attempts', 'SQL', 'V59__merchant_dashboard_and_verification_attempts.sql', 526735355, 'root', '2026-07-29 21:24:24.0', 28, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (60, '60', 'merchant management configuration', 'SQL', 'V60__merchant_management_configuration.sql', 204934765, 'root', '2026-07-30 14:42:08.0', 48, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (61, '61', 'huifu payment transactions', 'SQL', 'V61__huifu_payment_transactions.sql', -472346255, 'root', '2026-07-30 16:42:05.0', 58, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (62, '62', 'talent wechat sessions', 'SQL', 'V62__talent_wechat_sessions.sql', 1469730593, 'root', '2026-07-30 16:49:14.0', 26, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (63, '63', 'payment retry attempts', 'SQL', 'V63__payment_retry_attempts.sql', 787341541, 'root', '2026-07-31 10:14:44.0', 138, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (64, '64', 'backfill pending order expiry', 'SQL', 'V64__backfill_pending_order_expiry.sql', -1164474871, 'root', '2026-07-31 10:31:19.0', 10, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (65, '65', 'order create idempotency', 'SQL', 'V65__order_create_idempotency.sql', 1411270481, 'root', '2026-07-31 11:50:53.0', 145, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (66, '66', 'huifu refund transactions', 'SQL', 'V66__huifu_refund_transactions.sql', 1267247047, 'root', '2026-07-31 14:19:25.0', 543, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (67, '67', 'merchant verification reversal', 'SQL', 'V67__merchant_verification_reversal.sql', 334204971, 'root', '2026-07-31 15:38:24.0', 127, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (68, '68', 'merchant verification idempotency', 'SQL', 'V68__merchant_verification_idempotency.sql', 191823811, 'root', '2026-07-31 15:38:24.0', 36, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (69, '69', 'children entrustment school details', 'SQL', 'V69__children_entrustment_school_details.sql', 1910003241, 'root', '2026-07-31 15:38:24.0', 87, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (70, '70', 'merchant verification revoke idempotency', 'SQL', 'V70__merchant_verification_revoke_idempotency.sql', -1024625823, 'root', '2026-07-31 15:38:24.0', 28, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (71, '71', 'merchant multi credential verification', 'SQL', 'V71__merchant_multi_credential_verification.sql', 388538953, 'root', '2026-07-31 15:38:24.0', 120, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (72, '72', 'merchant manual consumption orders', 'SQL', 'V72__merchant_manual_consumption_orders.sql', -22824883, 'root', '2026-07-31 16:41:36.0', 193, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (73, '73', 'seed merchant manual consumption demo rule', 'SQL', 'V73__seed_merchant_manual_consumption_demo_rule.sql', 770596653, 'root', '2026-07-31 16:41:36.0', 4, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (74, '74', 'migrate legacy equity image categories', 'SQL', 'V74__migrate_legacy_equity_image_categories.sql', 1858492956, 'root', '2026-07-31 17:56:53.0', 16, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (75, '75', 'retire seeded demo merchants', 'SQL', 'V75__retire_seeded_demo_merchants.sql', 1073699067, 'root', '2026-07-31 18:13:30.0', 9, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (76, '76', 'admin operator authentication', 'SQL', 'V76__admin_operator_authentication.sql', -1107934867, 'root', '2026-07-31 18:23:36.0', 50, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (77, '77', 'talent advisor assignment', 'SQL', 'V77__talent_advisor_assignment.sql', 620357384, 'root', '2026-08-01 17:44:13.0', 132, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (78, '78', 'admin management configuration', 'SQL', 'V78__admin_management_configuration.sql', -788761022, 'root', '2026-08-01 19:36:57.0', 36, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (79, '79', 'repair food merchant text encoding', 'SQL', 'V79__repair_food_merchant_text_encoding.sql', 1196348806, 'root', '2026-08-01 20:09:49.0', 8, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (80, '80', 'merchant settlement workflow', 'SQL', 'V80__merchant_settlement_workflow.sql', 1716776309, 'root', '2026-08-03 10:13:52.0', 71, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (81, '81', 'payment reconciliation', 'SQL', 'V81__payment_reconciliation.sql', 2058647688, 'root', '2026-08-03 10:13:52.0', 18, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (82, '82', 'admin role permissions', 'SQL', 'V82__admin_role_permissions.sql', -1120804392, 'root', '2026-08-03 14:44:48.0', 81, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (83, '83', 'admin operator profile fields', 'SQL', 'V83__admin_operator_profile_fields.sql', -1543562906, 'root', '2026-08-03 14:44:48.0', 215, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (84, '84', 'huifu withdrawal binding', 'SQL', 'V84__huifu_withdrawal_binding.sql', -819423056, 'root', '2026-08-03 14:44:49.0', 85, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (85, '85', 'merchant rights configuration', 'SQL', 'V85__merchant_rights_configuration.sql', 1257240909, 'root', '2026-08-03 16:14:05.0', 119, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (86, '86', 'rename standard talent card to elite', 'SQL', 'V86__rename_standard_talent_card_to_elite.sql', -949505761, 'root', '2026-08-03 16:14:05.0', 7, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (87, '87', 'parking plate registration phase2', 'SQL', 'V87__parking_plate_registration_phase2.sql', 2033817652, 'root', '2026-08-04 18:35:28.0', 174, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (88, '88', 'ticket package code pool', 'SQL', 'V88__ticket_package_code_pool.sql', 352881850, 'root', '2026-08-04 18:35:28.0', 40, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (89, '89', 'merchant display policy events', 'SQL', 'V89__merchant_display_policy_events.sql', -343153799, 'root', '2026-08-04 18:35:28.0', 13, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (90, '90', 'dependent card apply status log', 'SQL', 'V90__dependent_card_apply_status_log.sql', -951264615, 'root', '2026-08-04 18:35:28.0', 12, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (91, '91', 'service application unified appointments', 'SQL', 'V91__service_application_unified_appointments.sql', -2058908979, 'root', '2026-08-04 18:35:29.0', 836, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (92, '92', 'admin approval organization extra info', 'SQL', 'V92__admin_approval_organization_extra_info.sql', -1835237241, 'root', '2026-08-04 18:40:33.0', 17, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (93, '93', 'merchant coupon distribution management', 'SQL', 'V93__merchant_coupon_distribution_management.sql', 357720156, 'root', '2026-08-06 16:14:09.0', 269, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (94, '94', 'service application operation log', 'SQL', 'V94__service_application_operation_log.sql', -1131973588, 'root', '2026-08-06 16:14:09.0', 38, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (95, '95', 'car wash credential audit', 'SQL', 'V95__car_wash_credential_audit.sql', -1720962674, 'root', '2026-08-06 16:14:09.0', 140, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (96, '96', 'parking plate operation log', 'SQL', 'V96__parking_plate_operation_log.sql', -775124057, 'root', '2026-08-06 16:14:09.0', 50, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (97, '98', 'merchant coupon talent level quantities', 'SQL', 'V98__merchant_coupon_talent_level_quantities.sql', -1997532057, 'root', '2026-08-06 16:16:11.0', 108, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (98, '99', 'merchant onboarding huifu kyc', 'SQL', 'V99__merchant_onboarding_huifu_kyc.sql', -2101049968, 'root', '2026-08-06 16:18:05.0', 99, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (99, '100', 'bank branch directory', 'SQL', 'V100__bank_branch_directory.sql', 1807573421, 'root', '2026-08-06 17:02:18.0', 50, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (100, '101', 'admin protocol settings', 'SQL', 'V101__admin_protocol_settings.sql', -1203779278, 'root', '2026-08-06 17:02:18.0', 20, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (101, '102', 'talent service staff assignment', 'SQL', 'V102__talent_service_staff_assignment.sql', -239485552, 'root', '2026-08-06 17:02:47.0', 58, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (102, '103', 'merchant bank card huifu business open', 'SQL', 'V103__merchant_bank_card_huifu_business_open.sql', -69951860, 'root', '2026-08-06 17:38:24.0', 77, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (103, '104', 'merchant bank card encrypted number', 'SQL', 'V104__merchant_bank_card_encrypted_number.sql', 1336670329, 'root', '2026-08-11 12:14:15.0', 139, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (104, '106', 'admin protocol four settings', 'SQL', 'V106__admin_protocol_four_settings.sql', -174261213, 'root', '2026-08-11 12:18:14.0', 31, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (105, '107', 'restore premium topic merchants', 'SQL', 'V107__restore_premium_topic_merchants.sql', 498121720, 'root', '2026-08-11 12:18:14.0', 13, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (106, '108', 'dependent card apply binding', 'SQL', 'V108__dependent_card_apply_binding.sql', 2135726729, 'root', '2026-08-11 12:18:15.0', 314, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (107, '109', 'dependent card apply schema compatibility', 'SQL', 'V109__dependent_card_apply_schema_compatibility.sql', 1039006436, 'root', '2026-08-11 12:18:15.0', 33, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (108, '110', 'sync admin protocol settings to agreements', 'SQL', 'V110__sync_admin_protocol_settings_to_agreements.sql', -718565758, 'root', '2026-08-11 12:18:15.0', 39, true);
INSERT INTO `flyway_schema_history` (`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, `installed_by`, `installed_on`, `execution_time`, `success`) VALUES (109, '111', 'news article top time sorting', 'SQL', 'V111__news_article_top_time_sorting.sql', -1596820028, 'root', '2026-08-11 12:19:32.0', 112, true);