| | |
| | | import org.springframework.jdbc.core.JdbcTemplate; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** Keeps merchant onboarding additive columns available when Flyway is disabled in the deployment. */ |
| | | /** Keeps merchant onboarding additive tables and columns available when Flyway is disabled in the deployment. */ |
| | | @Slf4j |
| | | @Component |
| | | public class MerchantOnboardingSchemaInitializer implements ApplicationRunner { |
| | | /** DDL 等锁上限:拿不到就放弃这一列,绝不把整个服务拖住。 */ |
| | | private static final int DDL_LOCK_WAIT_TIMEOUT_SECONDS = 5; |
| | | |
| | | /** |
| | | * 进件新增的表,与迁移里的建表语句逐字对齐(V144)。 |
| | | * |
| | | * <p>开发库(和任何 `spring.flyway.enabled=false` 的部署)不会自动补表:Flyway 不跑,而这个 |
| | | * 初始化器原来只会加列。结果是 V144 建的两张表在开发库永远不存在,`GET |
| | | * /api/merchant/onboarding/status` 的第一个请求就以 |
| | | * `Table 'talent_card.merchant_onboarding_progress' doesn't exist` 500——测试库有 Flyway, |
| | | * 看不出来。 |
| | | */ |
| | | private static final Map<String, String> TABLES = new LinkedHashMap<>(); |
| | | static { |
| | | TABLES.put("merchant_onboarding_progress", """ |
| | | create table if not exists merchant_onboarding_progress ( |
| | | application_id bigint not null primary key, |
| | | stages_json text not null, |
| | | last_synced_at datetime null, |
| | | sync_error varchar(512) not null default '', |
| | | sync_owner varchar(36) null, |
| | | sync_until datetime null, |
| | | updated_at datetime not null default current_timestamp on update current_timestamp |
| | | )"""); |
| | | TABLES.put("merchant_onboarding_attempt", """ |
| | | create table if not exists merchant_onboarding_attempt ( |
| | | id bigint not null auto_increment primary key, |
| | | application_id bigint not null, |
| | | step varchar(24) not null, |
| | | request_no varchar(32) not null, |
| | | request_date char(8) not null, |
| | | apply_no varchar(32) null, |
| | | status varchar(24) not null, |
| | | response_code varchar(32) null, |
| | | response_message varchar(512) null, |
| | | created_at datetime not null default current_timestamp, |
| | | unique key uk_onboarding_attempt_request (request_no), |
| | | key idx_onboarding_attempt_step (application_id, step, id) |
| | | )"""); |
| | | } |
| | | |
| | | private final JdbcTemplate jdbc; |
| | | |
| | | public MerchantOnboardingSchemaInitializer(JdbcTemplate jdbc) { this.jdbc = jdbc; } |
| | | |
| | | static Map<String, String> tableDdl() { return Map.copyOf(TABLES); } |
| | | |
| | | @Override |
| | | public void run(ApplicationArguments args) { |
| | | try { |
| | | for (Map.Entry<String, String> table : TABLES.entrySet()) { |
| | | if (!hasTable(table.getKey())) createTable(table.getKey(), table.getValue()); |
| | | } |
| | | Map<String, String> columns = new LinkedHashMap<>(); |
| | | columns.put("merchant_name", "varchar(128) default null"); |
| | | columns.put("merchant_short_name", "varchar(128) default null"); |
| | |
| | | } |
| | | } |
| | | |
| | | /** 与 {@link #addColumn} 同样地失败即放弃:缺表只影响进件进度,不能让整个服务起不来。 */ |
| | | private void createTable(String tableName, String ddl) { |
| | | try { |
| | | jdbc.execute("set session lock_wait_timeout = " + DDL_LOCK_WAIT_TIMEOUT_SECONDS); |
| | | jdbc.execute(ddl); |
| | | log.info("Merchant onboarding schema: created table {}", tableName); |
| | | } catch (Exception exception) { |
| | | log.warn("Merchant onboarding schema: could not create {} within {}s (table busy); it will be retried on the next restart. cause={}", |
| | | tableName, DDL_LOCK_WAIT_TIMEOUT_SECONDS, exception.getClass().getSimpleName()); |
| | | } |
| | | } |
| | | |
| | | private boolean hasTable(String tableName) throws Exception { |
| | | try (Connection connection = jdbc.getDataSource().getConnection(); |
| | | ResultSet result = connection.getMetaData().getTables(connection.getCatalog(), null, tableName, new String[] { "TABLE" })) { |
| | | if (result.next()) return true; |
| | | } |
| | | try (Connection connection = jdbc.getDataSource().getConnection(); |
| | | ResultSet result = connection.getMetaData().getTables(connection.getCatalog(), null, tableName.toUpperCase(), new String[] { "TABLE" })) { |
| | | return result.next(); |
| | | } |
| | | } |
| | | |
| | | private boolean hasColumn(String tableName, String columnName) throws Exception { |
| | | try (Connection connection = jdbc.getDataSource().getConnection(); ResultSet result = connection.getMetaData().getColumns(connection.getCatalog(), null, tableName, columnName)) { |
| | | if (result.next()) return true; |