Skip to content

源码:labs/flutter/host/lib/topics/backend_data/download_service_update_case_demo.dart ​

  • 原始路径:labs/flutter/host/lib/topics/backend_data/download_service_update_case_demo.dart
  • 类型:dart
dart
import 'dart:math';

import 'package:flutter/material.dart';

enum DownloadStage {
  idle,
  fetchingMetadata,
  downloading,
  verifying,
  promoting,
  completed,
  failed,
}

class DownloadServiceUpdateCaseDemoPage extends StatefulWidget {
  const DownloadServiceUpdateCaseDemoPage({super.key});

  static const routeName = '/backend_data/download_service_update_case';

  @override
  State<DownloadServiceUpdateCaseDemoPage> createState() =>
      _DownloadServiceUpdateCaseDemoPageState();
}

class _DownloadServiceUpdateCaseDemoPageState
    extends State<DownloadServiceUpdateCaseDemoPage> {
  DownloadStage _stage = DownloadStage.idle;
  final List<String> _logs = <String>['[idle] 当前 activeVersion=v41'];
  int _activeVersion = 41;
  int _remoteVersion = 42;
  bool _hashMatches = true;
  bool _promotionSucceeds = true;
  double _progress = 0;

  void _log(String message) {
    if (!mounted) return;
    setState(() {
      _logs.add(message);
    });
  }

  Future<void> _runUpdate() async {
    setState(() {
      _stage = DownloadStage.fetchingMetadata;
      _progress = 0;
    });
    _log('[fetchingMetadata] 拉取远端元信息: version=v$_remoteVersion, sha256=ok');
    await Future<void>.delayed(const Duration(milliseconds: 250));
    if (!mounted) return;

    setState(() {
      _stage = DownloadStage.downloading;
    });
    for (var step = 1; step <= 4; step++) {
      await Future<void>.delayed(const Duration(milliseconds: 200));
      if (!mounted) return;
      setState(() {
        _progress = step / 4;
      });
      _log('[downloading] staging/bundle_v$_remoteVersion.zip.part ${(100 * _progress).round()}%');
    }

    setState(() {
      _stage = DownloadStage.verifying;
    });
    _log('[verifying] 校验 size / sha256 / manifest');
    await Future<void>.delayed(const Duration(milliseconds: 300));
    if (!mounted) return;
    if (!_hashMatches) {
      setState(() {
        _stage = DownloadStage.failed;
      });
      _log('[failed] hashMismatch:删除 staging,activeVersion 仍保持 v$_activeVersion');
      return;
    }

    setState(() {
      _stage = DownloadStage.promoting;
    });
    _log('[promoting] 准备把 staging_v$_remoteVersion 原子切换为 active');
    await Future<void>.delayed(const Duration(milliseconds: 300));
    if (!mounted) return;
    if (!_promotionSucceeds) {
      setState(() {
        _stage = DownloadStage.failed;
      });
      _log('[failed] promotionFailure:指针未切换,继续回退到 activeVersion=v$_activeVersion');
      return;
    }

    setState(() {
      _activeVersion = _remoteVersion;
      _stage = DownloadStage.completed;
    });
    _log('[completed] current_version -> v$_activeVersion,旧版本进入 backup');
  }

  void _bumpRemoteVersion() {
    setState(() {
      _remoteVersion += 1 + Random().nextInt(2);
      _logs.add('[server] 远端版本更新为 v$_remoteVersion');
    });
  }

  void _reset() {
    setState(() {
      _stage = DownloadStage.idle;
      _activeVersion = 41;
      _remoteVersion = 42;
      _hashMatches = true;
      _promotionSucceeds = true;
      _progress = 0;
      _logs
        ..clear()
        ..add('[idle] 当前 activeVersion=v41');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('download_service 更新闭环')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('activeVersion=v$_activeVersion / remoteVersion=v$_remoteVersion'),
            const SizedBox(height: 8),
            LinearProgressIndicator(value: _stage == DownloadStage.downloading ? _progress : null),
            const SizedBox(height: 12),
            Wrap(
              spacing: 12,
              runSpacing: 8,
              children: [
                FilterChip(
                  label: const Text('hash 校验通过'),
                  selected: _hashMatches,
                  onSelected: (value) => setState(() => _hashMatches = value),
                ),
                FilterChip(
                  label: const Text('原子切换成功'),
                  selected: _promotionSucceeds,
                  onSelected: (value) => setState(() => _promotionSucceeds = value),
                ),
              ],
            ),
            const SizedBox(height: 12),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                ElevatedButton(onPressed: _runUpdate, child: const Text('执行更新')),
                OutlinedButton(onPressed: _bumpRemoteVersion, child: const Text('提升远端版本')),
                TextButton(onPressed: _reset, child: const Text('重置')),
              ],
            ),
            const SizedBox(height: 12),
            Expanded(
              child: ListView(
                children: [
                  Card(
                    child: Column(
                      children: DownloadStage.values
                          .map(
                            (stage) => ListTile(
                              dense: true,
                              leading: Icon(
                                stage == _stage ? Icons.play_circle_fill : Icons.check_circle_outline,
                              ),
                              title: Text(stage.name),
                              subtitle: Text(_labelFor(stage)),
                            ),
                          )
                          .toList(),
                    ),
                  ),
                  const SizedBox(height: 12),
                  Card(
                    color: Colors.black,
                    child: ListView.builder(
                      physics: const NeverScrollableScrollPhysics(),
                      shrinkWrap: true,
                      padding: const EdgeInsets.all(12),
                      itemCount: _logs.length,
                      itemBuilder: (context, index) => Text(
                        _logs[index],
                        style: const TextStyle(
                          color: Colors.greenAccent,
                          fontFamily: 'monospace',
                          fontSize: 12,
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
            Text(
              '观察重点:下载完成不等于更新成功,只有 verify + promote 都通过,activeVersion 才能切换。',
              style: Theme.of(context).textTheme.bodySmall,
            ),
          ],
        ),
      ),
    );
  }

  String _labelFor(DownloadStage stage) {
    return switch (stage) {
      DownloadStage.idle => '当前未在更新',
      DownloadStage.fetchingMetadata => '先拉 version/size/hash',
      DownloadStage.downloading => '只写 staging/.part,不碰 active',
      DownloadStage.verifying => '校验长度、sha256、manifest',
      DownloadStage.promoting => '原子替换 active 目录与版本指针',
      DownloadStage.completed => '新版本已生效,旧版本进入 backup',
      DownloadStage.failed => '失败后保留旧版本继续服务',
    };
  }
}

站点构建时间:2026/8/24 23:43:17