更新的难点不在下载,而在「发布之后客户端能不能收到」。这里曾经出过一次典型问题:后台发布只往数据库写一行,而客户端读的是一个静态 update.json 文件 —— 文件没上传就 404,表现为「后台显示发布成功,客户端却一直说已是最新版」。
三级回退
- 云端动态接口 —— 直接读数据库,后台发布后立即生效,无需上传任何文件(主通道)
- 云端静态清单
update.json—— 老流程,保留兼容 - exe 同目录的
update.json—— 离线、内网、云端全挂时的兜底
// ① 动态接口(主通道)
if (!customSrc && !g_cloudServer.empty()) {
string r = CloudProxyForward("/api/activate", "", "op=version",
"application/x-www-form-urlencoded");
if (LooksLikeJson(r)) {
string v0 = JsonStr(r, "version");
if (!v0.empty()) { ver = v0; url = JsonStr(r, "url"); size = atoll(...); source = "cloud-api"; }
}
}
// ② 云端静态清单 → ③ 本地清单,逐级回退
if (source.empty() && !manifestUrl.empty()) { ... source = "cloud-json"; }
if (source.empty()) { ReadLocalUpdateManifest(); source = "local"; }响应里带 source 字段标明版本信息来自哪一级。排查用户问题时,一眼就能看出他卡在哪个通道。
后台发布时顺手导出静态清单
为了让还没更新的老客户端也能收到通知,后台在发布或删除版本时会自动把最新一行导出成 update.json:
function cdc_export_update_manifest(?PDO $pdo = null): bool {
$row = $pdo->query("SELECT * FROM updates ORDER BY created_at DESC, id DESC LIMIT 1")->fetch();
$man = [
'version' => $row ? (string)$row['version_name'] : APP_VERSION,
'url' => $row ? (string)$row['download_url'] : UPDATE_URL,
'size' => $row ? (int)($row['size'] ?? 0) : 0,
'sha256' => $row ? (string)($row['sha256'] ?? '') : '',
'force' => $row ? ((int)($row['force'] ?? 0) === 1) : false,
'notice' => $notes, 'notes' => $notes,
];
return @file_put_contents(__DIR__ . '/update.json',
json_encode($man, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n", LOCK_EX) !== false;
}下载完成后怎么找到安装包
- 下载跑在独立线程 —— 主服务不阻塞,界面靠轮询进度接口显示百分比
- 入口必须是安装包直链 —— 「下载网址」填了官网首页这种地址会被拦下,避免把 HTML 存成 exe
- 探测用 GET 加
Range: bytes=0-0,不用 HEAD —— 很多面板对 HEAD 直接返回 405 - 按「文件名特征 + 最近 72 小时内最新」双保险找包 —— 服务器给安装包改个名字也照样认得出来
- 「已安装」必须校验卸载器真实存在 —— 注册表里残留一条卸载项并不代表软件还在,误判会让更新流程跳过真正的安装