yeg
2 天以前 ff4525af5b4178a38ca310719e4c7af9af3e742c
talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantOnboardingSchemaInitializer.java
@@ -114,6 +114,26 @@
            columns.put("capability_response_code", "varchar(32) default null");
            columns.put("capability_response_message", "varchar(512) default null");
            columns.put("capability_verified_at", "datetime default null");
            // 商户端「立即核实」的冷却时间戳,见 V148。
            columns.put("capability_sync_until", "datetime default null");
            // 法人手机号:微信实名认证走法人时的必填项,实名接口的 name/mobile/id_card_number
            // 都必须是法人本人的,不能用联系人手机号顶替。见 V145 的说明。
            columns.put("legal_mobile", "varchar(20) default null");
            columns.put("realname_status", "varchar(16) not null default 'not_started'");
            columns.put("realname_request_no", "varchar(32) default null");
            columns.put("realname_apply_no", "varchar(32) default null");
            columns.put("realname_response_code", "varchar(32) default null");
            columns.put("realname_response_message", "varchar(512) default null");
            // 门店照片:线下经营的门店材料是**一组三张**(门头照 F22、内景照 F24、收银台 F105),
            // 通道在进件这一步就按 F-code 逐张核对,企业与个人主体都要交。见 V146 与 V156 的说明。
            columns.put("store_header_url", "varchar(512) default null");
            columns.put("store_indoor_url", "varchar(512) default null");
            columns.put("store_cashier_url", "varchar(512) default null");
            // 经营详细地址:线下进件的 detail_addr 是通道必填项,与证件地址分开存。见 V155 的说明。
            columns.put("business_address", "varchar(255) default null");
            // 进件来源(自助进件 / 后台纳管):决定进度刷新是否允许下发通道改动。见 V158 的说明。
            // 开发库(flyway 关闭)靠这里补列,否则纳管的 INSERT 会报 Unknown column。
            columns.put("source", "varchar(16) not null default 'self'");
            for (Map.Entry<String, String> column : columns.entrySet()) {
                if (!hasColumn("merchant_onboarding_application", column.getKey())) addColumn("merchant_onboarding_application", column.getKey(), column.getValue());
            }
@@ -157,8 +177,16 @@
            jdbc.execute("alter table " + tableName + " add column " + columnName + " " + definition);
            log.info("Merchant onboarding schema: added {}.{}", tableName, columnName);
        } catch (Exception exception) {
            log.warn("Merchant onboarding schema: could not add {}.{} within {}s (table busy); it will be retried on the next restart. cause={}",
                    tableName, columnName, DDL_LOCK_WAIT_TIMEOUT_SECONDS, exception.getClass().getSimpleName());
            // 这里必须是 error 而不是 warn:进件的 INSERT/UPDATE 都显式列出列名,缺列不是"只影响重试功能",
            // 而是商户一提交进件就报 Unknown column。原先只记 warn、应用照常启动,于是症状变成
            // "重启了还是报错、又看不出原因"(2026-09-16 在开发库的 legal_mobile 上实际发生过:
            // 同一轮循环里它后面的列都加成功了,只有它抢锁失败)。日志里直接给出补救语句。
            log.error("Merchant onboarding schema: could not add {}.{} within {}s (table busy); the column is"
                            + " referenced by the onboarding INSERT, so onboarding submissions will fail until it"
                            + " exists. Retried on the next restart, or run manually: alter table {} add column {} {}."
                            + " cause={}",
                    tableName, columnName, DDL_LOCK_WAIT_TIMEOUT_SECONDS, tableName, columnName, definition,
                    exception.getClass().getSimpleName());
        }
    }