diff --git a/server-rs/crates/preview-deployer-server/src/lib.rs b/server-rs/crates/preview-deployer-server/src/lib.rs index b397ed480..223255b1b 100644 --- a/server-rs/crates/preview-deployer-server/src/lib.rs +++ b/server-rs/crates/preview-deployer-server/src/lib.rs @@ -1052,6 +1052,7 @@ fn load_deployments(config: &Config) -> Result if record.public.id.as_deref() == Some(record.instance_id.as_str()) { record.public.id = None; } + recover_legacy_build_details(&mut record.public, config); validate_deployment_id(&record.instance_id) .map_err(|_| "状态文件包含无效预览实例 ID".to_string())?; if let Some(build_id) = record.public.id.as_deref() { @@ -1269,6 +1270,38 @@ fn validate_record_id(value: &str) -> Result<(), ApiError> { .ok_or_else(|| ApiError::bad_request("构建编号无效")) } +fn recover_legacy_build_details(deployment: &mut Deployment, config: &Config) { + let Some(build_url) = deployment.jenkins_build_url.as_deref() else { + return; + }; + let Some(number) = jenkins_build_number(build_url, &config.jenkins_base_url) else { + deployment.jenkins_build_url = None; + return; + }; + if deployment.id.is_none() { + deployment.id = Some(number.to_string()); + } + deployment.jenkins_build_url = config + .jenkins_public_base_url + .join(&format!("{number}/")) + .ok() + .map(|url| url.to_string()); +} + +fn jenkins_build_number(value: &str, job_url: &Url) -> Option { + let parsed = Url::parse(value).ok()?; + if !parsed.username().is_empty() || parsed.password().is_some() { + return None; + } + parsed + .path() + .strip_prefix(job_url.path())? + .trim_end_matches('/') + .parse::() + .ok() + .filter(|number| *number > 0) +} + fn map_artifact_status(value: &str) -> Option { match value.to_ascii_uppercase().as_str() { "QUEUED" => Some(DeploymentStatus::Queued), diff --git a/server-rs/crates/preview-deployer-server/src/tests.rs b/server-rs/crates/preview-deployer-server/src/tests.rs index b22bd6e12..98b2b5511 100644 --- a/server-rs/crates/preview-deployer-server/src/tests.rs +++ b/server-rs/crates/preview-deployer-server/src/tests.rs @@ -698,6 +698,7 @@ fn legacy_running_state_recovers_web_port_from_validated_url() { "status": "running", "health": "healthy", "webUrl": "http://192.168.35.82:8407", + "jenkinsBuildUrl": "http://127.0.0.1:18080/jenkins/job/shared/job/Genarrative-Preview-Deployer/42/", "createdAt": 1, "updatedAt": 2, "canUninstall": true @@ -720,6 +721,18 @@ fn legacy_running_state_recovers_web_port_from_validated_url() { .web_port, Some(8407) ); + let deployment = state + .deployments + .blocking_read() + .get(&id) + .unwrap() + .public + .clone(); + assert_eq!(deployment.id.as_deref(), Some("42")); + assert_eq!( + deployment.jenkins_build_url.as_deref(), + Some("http://192.168.35.82:8080/jenkins/job/shared/job/Genarrative-Preview-Deployer/42/") + ); std::fs::remove_file(&state.config.state_file).unwrap(); }