Nebula 1.1.0 最值得开发者关注的变化,不只是“支持了第二家云”。更关键的是:华为云 OBS 从零手写独立 Rust SDK,并通过真实账号完成浏览、上传、下载、分享、传输等全链路验证后,仍然复用了阿里云 OSS 的同一套业务逻辑。新增云厂商时,应用界面一行未改,这说明抽象层终于经受住了第二个实现的检验。
第二家云为什么是架构分水岭
只支持一家云时,很多“抽象”其实还只是命名好看的封装。字段名、分页方式、签名逻辑、对象路径语义、错误格式,都可能悄悄泄漏到 UI 和业务流程里。等第二家云接入时,这些泄漏会集中爆炸。
Nebula 1.1.0 的价值在于,华为云 OBS 接入后没有为桌面界面单独开分支。浏览、上传下载、分享、传输逻辑继续走同一套链路,说明应用层面对的是“对象存储能力”,而不是某一家厂商的 API 形状。
对开发团队来说,这类边界很实用:
- UI 只关心 bucket、object、目录感、传输状态、分享链接。
- 云厂商 SDK 负责签名、鉴权、HTTP 请求、错误解析。
- 中间的 provider 层把不同云的差异压成稳定接口。
- 新增云时优先扩展 provider,而不是改每一个按钮和页面。
这不是为了追求“优雅”,而是为了让第三家云、第四家云接入时,改动范围可预测。
手写 Rust SDK 的取舍
摘要里提到,华为云 OBS SDK 是从零手写的独立 Rust SDK,并且经过真实账号全链路验证。这类选择通常意味着团队想掌控几个关键点:依赖体积、跨平台桌面端兼容性、签名流程、错误模型,以及和现有传输系统的贴合程度。
手写 SDK 的收益明显:可以只实现产品真正需要的能力,把接口设计成应用友好的形状。但风险也同样具体:云厂商 API 的边界条件很多,签名细节、区域 endpoint、特殊字符编码、大文件上传、失败重试、权限错误,都需要测试覆盖。真实账号全链路验证是必要步骤,但后续仍然需要把这些路径沉淀成自动化用例。
可以这样实践:在多云应用里,不要让 UI 直接依赖具体 SDK。先定义一个足够小的对象存储接口,再让 OSS、OBS 分别实现它。
use async_trait::async_trait;
#[derive(Debug, Clone)]
pub struct ObjectMeta {
pub key: String,
pub size: u64,
}
#[derive(Debug, Clone)]
pub struct ShareLink {
pub url: String,
pub expires_in_seconds: u64,
}
#[async_trait]
pub trait ObjectStorage: Send + Sync {
async fn list_objects(&self, bucket: &str, prefix: &str) -> anyhow::Result<Vec<ObjectMeta>>;
async fn upload_object(&self, bucket: &str, key: &str, bytes: Vec<u8>) -> anyhow::Result<()>;
async fn download_object(&self, bucket: &str, key: &str) -> anyhow::Result<Vec<u8>>;
async fn create_share_link(&self, bucket: &str, key: &str, ttl_seconds: u64) -> anyhow::Result<ShareLink>;
}
pub struct ObsStorage {
pub endpoint: String,
pub access_key: String,
pub secret_key: String,
}
#[async_trait]
impl ObjectStorage for ObsStorage {
async fn list_objects(&self, bucket: &str, prefix: &str) -> anyhow::Result<Vec<ObjectMeta>> {
// 可以在这里封装 OBS 的签名、分页和 XML/JSON 解析。
// UI 层不应该知道这些细节。
println!("list obs://{}/{} via {}", bucket, prefix, self.endpoint);
Ok(vec![])
}
async fn upload_object(&self, bucket: &str, key: &str, bytes: Vec<u8>) -> anyhow::Result<()> {
println!("upload {} bytes to obs://{}/{}", bytes.len(), bucket, key);
Ok(())
}
async fn download_object(&self, bucket: &str, key: &str) -> anyhow::Result<Vec<u8>> {
println!("download obs://{}/{}", bucket, key);
Ok(Vec::new())
}
async fn create_share_link(&self, bucket: &str, key: &str, ttl_seconds: u64) -> anyhow::Result<ShareLink> {
println!("share obs://{}/{} for {}s", bucket, key, ttl_seconds);
Ok(ShareLink {
url: "https://example.invalid/signed-url".to_string(),
expires_in_seconds: ttl_seconds,
})
}
}
上面的代码不是 Nebula 源码,只是一个可以改造的最小形状。重点是 trait 的边界:它描述产品需要什么,而不是复制某家云厂商的接口。
用配置切换云,而不是用界面分叉
当应用已经有两家云时,账号配置也要跟着变清楚。一个可维护的做法是把 provider 类型、endpoint、region、凭据引用和 bucket 信息放进配置层,让 UI 只拿到统一的连接对象。
可以这样实践一个本地开发配置:
# storage.accounts.yaml
accounts:
- id: aliyun-main
provider: oss
display_name: Aliyun OSS Main
endpoint: https://oss-cn-hangzhou.aliyuncs.com
bucket: my-oss-bucket
credentials_env:
access_key_id: ALIYUN_ACCESS_KEY_ID
access_key_secret: ALIYUN_ACCESS_KEY_SECRET
- id: huawei-backup
provider: obs
display_name: Huawei OBS Backup
endpoint: https://obs.cn-north-4.myhuaweicloud.com
bucket: my-obs-bucket
credentials_env:
access_key_id: HUAWEI_ACCESS_KEY_ID
access_key_secret: HUAWEI_ACCESS_KEY_SECRET
运行前需要把 bucket、endpoint 和环境变量名替换成自己的值。桌面应用可以在启动时加载这类配置,再通过 provider 字段选择具体实现:OSS 实现或 OBS 实现。这样“新增一家云”的影响集中在配置解析和 provider 注册处,而不是扩散到文件列表、上传面板、分享弹窗、传输队列。
如果要做一个很小的冒烟测试,也可以把关键路径变成命令行检查:
export HUAWEI_ACCESS_KEY_ID="your-access-key-id"
export HUAWEI_ACCESS_KEY_SECRET="your-access-key-secret"
nebula storage list --account huawei-backup --prefix "test/"
nebula storage upload --account huawei-backup ./README.md "test/README.md"
nebula storage share --account huawei-backup "test/README.md" --ttl 3600
nebula storage download --account huawei-backup "test/README.md" ./README.downloaded.md
这里的 nebula storage ... 命令是面向实践的示例,具体命令名需要按项目实际 CLI 调整。它表达的是验证顺序:列目录、上传、分享、下载,覆盖对象存储最核心的闭环。
桌面体验打磨不是“小修小补”
这一版还调整了自定义应用菜单、关于弹窗、侧栏体验与浅色主题。对桌面端工具来说,这些看似不如“第二家云”硬核,但会直接影响用户是否愿意长期打开它。
应用菜单决定了桌面软件是否像一个“本地应用”,而不是网页壳。关于弹窗承载版本、版权、更新信息,尤其适合在 1.1.0 这类版本里清楚展示能力变化。侧栏体验影响账号、bucket、目录层级之间的切换效率。浅色主题则考验细节:边框、悬停、选中态、禁用态、分隔线,如果只是把深色主题反转,通常会显得灰、散、刺眼。
多云能力和桌面体验放在同一版里并不冲突。前者让产品能覆盖更多存储场景,后者让用户在日常操作里少分心。
接入下一家云前的检查清单
Nebula 1.1.0 的经验可以压缩成一张工程检查表:
- UI 是否只依赖统一的对象存储接口?
- 上传、下载、分享、传输队列是否没有 provider 分支?
- SDK 错误是否被转换成产品能展示的人类可读错误?
- 分页、重试、超时、取消传输是否在 provider 层有一致语义?
- 是否用真实账号跑过浏览、上传、下载、分享全链路?
- 是否有最小自动化测试覆盖签名、路径编码和权限失败?
- 新增账号是否只需要配置和凭据,而不是新增界面?
第二家云接入成功,才真正说明抽象层不是纸面设计。Nebula 1.1.0 的信号很明确:多云不是在界面上堆入口,而是在底层把差异收束起来,让用户继续用同一套操作完成工作。