feat(sync): 云同步指向专用端口 apic.openless.top:9443 并补充服务端交接文档

- MarketplaceConfig 新增 cloud_sync_base_url:production 用同一市场主机的独立端口 9443,new() 默认同源以兼容本机假服务合同测试
- cloud_sync 改用 cloud_sync_url 生成 /me/sync 请求地址
- 新增生产同步地址合同测试(主机一致、端口 9443、HTTPS)
- docs:cloud-sync.md 更新地址说明;新增 cloud-sync-server-handoff.md 交接文档(请求行为、状态码映射、验收清单)
This commit is contained in:
sim committed 2026-09-10 21:17:47 +08:00
1 parent 00914cebc5
commit 110f1f631f
7 files changed
+153 -5

No files matched your search

@@ -154,7 +154,7 @@ impl CloudSyncService {
body: Option<Vec<u8>>,
token: &SecretValue,
) -> Result<CloudSyncSnapshot, BackendError> {
let url = self.marketplace.public_url("me/sync")?;
let url = self.marketplace.cloud_sync_url("me/sync")?;
let loopback = url.host_str().is_some_and(|host| {
host == "localhost"
|| host
@@ -267,7 +267,9 @@ pub use local_asr_service::{
LocalAsrRuntimeLease, ModelPrepareProgressSink, ModelRuntimeAdapter, NativeModelState,
StorageRebind,
};
pub use marketplace::{MarketplaceConfig, MARKETPLACE_BASE_URL, MARKETPLACE_GITHUB_TOKEN_ACCOUNT};
pub use marketplace::{
MarketplaceConfig, CLOUD_SYNC_BASE_URL, MARKETPLACE_BASE_URL, MARKETPLACE_GITHUB_TOKEN_ACCOUNT,
};
pub use model_store::{
extract_archive_safely, merge_hf_tree_pages, merge_hf_tree_pages_with_base, model_mirror_base,
parse_hf_tree_page, validate_model_path, validate_model_url, DownloadProgressSink,
@@ -20,10 +20,14 @@ use crate::{PreferencesStore, StylePack, StylePackStore};
pub const MARKETPLACE_GITHUB_TOKEN_ACCOUNT: &str = "github.oauth_token";
pub const MARKETPLACE_BASE_URL: &str = "https://apic.openless.top";
/// Cloud sync shares the marketplace host but runs on its own listener port;
/// the sync service owner deploys TLS on this port.
pub const CLOUD_SYNC_BASE_URL: &str = "https://apic.openless.top:9443";
#[derive(Debug, Clone)]
pub struct MarketplaceConfig {
pub base_url: reqwest::Url,
pub cloud_sync_base_url: reqwest::Url,
pub github_client_id: String,
pub github_device_code_url: reqwest::Url,
pub github_access_token_url: reqwest::Url,
@@ -31,7 +35,17 @@ pub struct MarketplaceConfig {
}
impl MarketplaceConfig {
/// The cloud sync endpoint defaults to the given base URL so contract tests
/// can point both services at one local mock; production pairs the
/// marketplace host with the dedicated [`CLOUD_SYNC_BASE_URL`].
pub fn new(base_url: impl AsRef<str>) -> Result<Self, BackendError> {
Self::with_cloud_sync_base_url(base_url, None)
}
pub fn with_cloud_sync_base_url(
base_url: impl AsRef<str>,
cloud_sync_base_url: Option<&str>,
) -> Result<Self, BackendError> {
let parse = |value: &str, name: &str| {
reqwest::Url::parse(value).map_err(|_| {
BackendError::new(
@@ -46,6 +60,10 @@ impl MarketplaceConfig {
.unwrap_or_else(|| "Ov23liyv3nEucG7oMHNE".into());
Ok(Self {
base_url: parse(base_url.as_ref(), "marketplace")?,
cloud_sync_base_url: match cloud_sync_base_url {
Some(value) => parse(value, "cloud sync")?,
None => parse(base_url.as_ref(), "marketplace")?,
},
github_client_id,
github_device_code_url: parse(
"https://github.com/login/device/code",
@@ -60,7 +78,8 @@ impl MarketplaceConfig {
}
pub fn production() -> Self {
Self::new(MARKETPLACE_BASE_URL).expect("built-in Marketplace URLs are valid")
Self::with_cloud_sync_base_url(MARKETPLACE_BASE_URL, Some(CLOUD_SYNC_BASE_URL))
.expect("built-in Marketplace URLs are valid")
}
}
@@ -264,6 +283,15 @@ impl MarketplaceService {
})
}
pub(crate) fn cloud_sync_url(&self, path: &str) -> Result<reqwest::Url, BackendError> {
self.config.cloud_sync_base_url.join(path).map_err(|_| {
BackendError::new(
BackendErrorCode::InvalidArgument,
"invalid cloud sync request URL",
)
})
}
async fn send_with_retry<F>(&self, make: F) -> Result<reqwest::Response, BackendError>
where
F: Fn() -> reqwest::RequestBuilder,
@@ -456,3 +456,26 @@ async fn unsupported_server_auth_failures_and_logout_do_not_invent_success() {
);
assert_eq!(server.state.lock().unwrap().requests.len(), count);
}
#[test]
fn production_cloud_sync_targets_the_dedicated_port_on_the_marketplace_host() {
let config = MarketplaceConfig::production();
assert_eq!(
config.cloud_sync_base_url,
reqwest::Url::parse(openless_core::CLOUD_SYNC_BASE_URL).unwrap()
);
assert_eq!(
config.cloud_sync_base_url.host_str(),
config.base_url.host_str(),
"cloud sync must stay on the marketplace host"
);
assert_eq!(config.cloud_sync_base_url.port(), Some(9443));
assert_eq!(
config.cloud_sync_base_url.scheme(),
"https",
"the production sync endpoint must be HTTPS"
);
// Contract tests point both services at one local mock through `new`.
let local = MarketplaceConfig::new("http://127.0.0.1:8080/").unwrap();
assert_eq!(local.cloud_sync_base_url.as_str(), "http://127.0.0.1:8080/");
}