新增: 应用内版本更新与自动升级全链路

后端 update.rs: app_check_update(拉 latest.json+ed25519 签名校验) /
app_download_install_update / prefs 读写 / mark_checked / mark_notified 五命令,
lib.rs 注册;tauri.conf updater 段(endpoints=app.1216.top/download/devflow/latest.json
+ pubkey + createUpdaterArtifacts) + tauri-plugin-updater 依赖 + capabilities 权限。

前端 UpdateBadge.vue(顶栏徽章+下拉) / useUpdateCheck.ts(检查/下载/安装状态机) /
AboutPanel.vue(设置页关于面板) + AppLayout 接线 + SettingsNav/searchIndex 索引 +
i18n zh/en settings 键。

运维 scripts/publish-release.ps1(发版) + swap-install.ps1(换装校验) +
设计文档 版本更新与自动升级-设计-2026-10-09.md。

验收: cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + build EXIT 0。
This commit is contained in:
lxy
2026-10-09 22:53:14 +08:00
parent c70faa7bb8
commit a8c92e9619
27 changed files with 2021 additions and 24 deletions
+7 -3
View File
@@ -70,10 +70,12 @@ impl Node for HumanNode {
// 3. select! 循环:Response / 超时 / 取消
let deadline = tokio::time::Instant::now() + Duration::from_secs(timeout_secs);
let mut cancel_tick = tokio::time::interval(Duration::from_millis(500));
cancel_tick.tick().await; // 丢弃首个立即触发的 tick
// FR-P4:取消检测由 500ms interval 轮询改为 Notify 被动唤醒(set_cancelled 触发)。
// 先建 notified future 再进 loop,避免 notify 早于 await 的竞态窗口;
// 每轮 select! 后重建(Notified 单次消费),配合 is_cancelled 复查兜底。
loop {
let cancel_wait = ctx.node_status.cancel_notified();
tokio::select! {
recv = rx.recv() => match recv {
Ok(WorkflowEvent::HumanApprovalResponse {
@@ -154,7 +156,9 @@ impl Node for HumanNode {
_ = tokio::time::sleep_until(deadline) => {
return Err(anyhow::anyhow!("人工审批超时({}s)", timeout_secs));
}
_ = cancel_tick.tick() => {
_ = cancel_wait => {
// FR-P4:Notify 唤醒后复查 is_cancelled(Notify 无状态,
// 可能因其他节点取消而误唤醒,须确认本节点确被取消)。
if ctx.node_status.is_cancelled(&ctx.node_id) {
return Err(anyhow::anyhow!("人工审批被取消"));
}
+18
View File
@@ -30,6 +30,10 @@ use df_types::types::{NodeId, NodeStatus};
pub struct StateMachine {
/// 节点状态映射(Arc 共享,clone 浅拷贝同一 HashMap)
states: Arc<Mutex<HashMap<NodeId, NodeStatus>>>,
/// 取消信号唤醒器(FR-P4):set_cancelled 时 notify_waiters,
/// 阻塞节点(HumanNode)用 notified() 被动等待,替代 500ms 轮询。
/// 与 states 同 Arc 共享,clone 浅拷贝同一 Notify。
cancel_notify: Arc<tokio::sync::Notify>,
}
impl StateMachine {
@@ -37,9 +41,19 @@ impl StateMachine {
pub fn new() -> Self {
Self {
states: Arc::new(Mutex::new(HashMap::new())),
cancel_notify: Arc::new(tokio::sync::Notify::new()),
}
}
/// 获取取消信号等待器(FR-P4)
///
/// 返回的 `Notified` future 在 `set_cancelled` 被调用时 resolve。
/// 调用方应配合 `is_cancelled` 做条件判断(Notify 无状态,可能错过
/// 早于 await 的 notify,故 select! 分支 resolve 后仍须复查 is_cancelled)。
pub fn cancel_notified(&self) -> tokio::sync::futures::Notified<'_> {
self.cancel_notify.notified()
}
/// 获取节点状态
///
/// 锁中毒时降级返回 `NodeStatus::Pending`(保守默认:视为未启动,
@@ -139,6 +153,10 @@ impl StateMachine {
);
}
}
// FR-P4:唤醒所有等待取消信号的阻塞节点(HumanNode notified 分支)。
// notify_waiters 不持锁调用,避免与 states 锁耦合;Notify 无状态,
// 等待方 resolve 后仍复查 is_cancelled 兜底(防 notify 早于 await 丢失)。
self.cancel_notify.notify_waiters();
}
/// 获取所有状态快照(clone 返回,调用方持独立副本)