diff --git a/update-service/src/main/resources/db/migration/V7__repair_baselined_rn_bundle_contract.sql b/update-service/src/main/resources/db/migration/V7__repair_baselined_rn_bundle_contract.sql new file mode 100644 index 0000000..a001df4 --- /dev/null +++ b/update-service/src/main/resources/db/migration/V7__repair_baselined_rn_bundle_contract.sql @@ -0,0 +1,126 @@ +-- 部分早期私有化库将 Flyway baseline 直接设置为 V3,但没有执行 V3 的表结构变更。 +-- 本迁移按实际字段状态补齐终态契约;已正确执行 V3 的数据库重复运行时不会改坏结构。 +DELIMITER // + +CREATE PROCEDURE repair_baselined_rn_bundle_contract() +BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'md5' + ) THEN + ALTER TABLE update_rn_bundle DROP COLUMN md5; + END IF; + + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'min_common_version' + ) THEN + ALTER TABLE update_rn_bundle DROP COLUMN min_common_version; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'module_type' + ) THEN + ALTER TABLE update_rn_bundle ADD COLUMN module_type VARCHAR(16) NULL AFTER module_id; + END IF; + + ALTER TABLE update_rn_bundle MODIFY COLUMN version VARCHAR(64) NOT NULL; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'app_version_range' + ) THEN + ALTER TABLE update_rn_bundle ADD COLUMN app_version_range VARCHAR(128) NULL AFTER version; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'built_against_native_baseline_id' + ) THEN + ALTER TABLE update_rn_bundle ADD COLUMN built_against_native_baseline_id VARCHAR(128) NULL AFTER app_version_range; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'build_id' + ) THEN + ALTER TABLE update_rn_bundle ADD COLUMN build_id VARCHAR(128) NULL AFTER built_against_native_baseline_id; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'bundle_format' + ) THEN + ALTER TABLE update_rn_bundle ADD COLUMN bundle_format VARCHAR(32) NULL AFTER build_id; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'common_version_range' + ) THEN + ALTER TABLE update_rn_bundle ADD COLUMN common_version_range VARCHAR(128) NULL AFTER bundle_format; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'min_native_api_level' + ) THEN + ALTER TABLE update_rn_bundle ADD COLUMN min_native_api_level INT NULL AFTER common_version_range; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'bundle_sha256' + ) THEN + ALTER TABLE update_rn_bundle ADD COLUMN bundle_sha256 VARCHAR(64) NULL AFTER min_native_api_level; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'archive_sha256' + ) THEN + ALTER TABLE update_rn_bundle ADD COLUMN archive_sha256 VARCHAR(64) NULL AFTER bundle_sha256; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'signed_manifest' + ) THEN + ALTER TABLE update_rn_bundle ADD COLUMN signed_manifest TEXT NULL AFTER bundle_url; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'signing_key_id' + ) THEN + ALTER TABLE update_rn_bundle ADD COLUMN signing_key_id VARCHAR(64) NULL AFTER signed_manifest; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' AND column_name = 'signature' + ) THEN + ALTER TABLE update_rn_bundle ADD COLUMN signature VARCHAR(256) NULL AFTER signing_key_id; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.statistics + WHERE table_schema = DATABASE() AND table_name = 'update_rn_bundle' + AND index_name = 'idx_rn_release_candidates' + ) THEN + ALTER TABLE update_rn_bundle + ADD INDEX idx_rn_release_candidates + (app_key, platform, module_id, publish_status, created_at); + END IF; + + -- 旧包没有不可变清单、归档摘要和平台签名,不得成为客户端候选版本。 + UPDATE update_rn_bundle + SET publish_status = 'DEPRECATED' + WHERE archive_sha256 IS NULL OR signed_manifest IS NULL OR signature IS NULL; +END// + +CALL repair_baselined_rn_bundle_contract()// +DROP PROCEDURE repair_baselined_rn_bundle_contract// + +DELIMITER ; diff --git a/update-service/src/test/java/com/xuqm/update/config/MigrationSchemaContractTest.java b/update-service/src/test/java/com/xuqm/update/config/MigrationSchemaContractTest.java index e61207c..1c1ff45 100644 --- a/update-service/src/test/java/com/xuqm/update/config/MigrationSchemaContractTest.java +++ b/update-service/src/test/java/com/xuqm/update/config/MigrationSchemaContractTest.java @@ -29,6 +29,38 @@ class MigrationSchemaContractTest { ); } + @Test + void baselinedPrivateDatabaseRepairContainsTerminalBundleIdentity() throws IOException { + var resource = getClass().getResourceAsStream( + "/db/migration/V7__repair_baselined_rn_bundle_contract.sql" + ); + assertTrue(resource != null, "V7 repair migration must be packaged"); + + String sql; + try (resource) { + sql = new String(resource.readAllBytes(), StandardCharsets.UTF_8); + } + + for (String column : new String[] { + "module_type", + "app_version_range", + "built_against_native_baseline_id", + "build_id", + "bundle_format", + "common_version_range", + "min_native_api_level", + "bundle_sha256", + "archive_sha256", + "signed_manifest", + "signing_key_id", + "signature" + }) { + assertTrue(sql.contains("column_name = '" + column + "'"), column + " must be repaired"); + } + assertTrue(sql.contains("publish_status = 'DEPRECATED'")); + assertTrue(sql.contains("idx_rn_release_candidates")); + } + private static int occurrences(String value, String token) { int count = 0; int offset = 0;