- 标注:AI 预标注直写 labels_json(去候选确认两阶段);重叠去重(minIoU);全量标注按钮 - 训练:脚本迁移入 server/training/(Go 化 prepare_yolo/analyze_rfdetr,保留 train_server.py);tflite 产物自检并入训练流程(check_tflite) - 数据目录/权重不进 git;.gitignore 迁移至仓库根
87 lines
3.0 KiB
Dart
87 lines
3.0 KiB
Dart
import 'dart:convert';
|
||
import 'dart:io';
|
||
|
||
import 'package:http/http.dart' as http;
|
||
|
||
import '../config/app_config.dart';
|
||
|
||
/// App 版本更新信息(GET /api/v1/app/update 响应 data;无记录时为空信息)
|
||
class AppUpdateInfo {
|
||
final String version;
|
||
final String notes;
|
||
|
||
const AppUpdateInfo({required this.version, required this.notes});
|
||
|
||
/// 服务器无版本记录时返回空信息,调用方视为无需更新
|
||
bool get isEmpty => version.isEmpty;
|
||
}
|
||
|
||
/// 启动时版本更新检查:仅 Android 检查;服务器版本高于本地版本即强制更新
|
||
/// (无普通/强制之分)。
|
||
class UpdateChecker {
|
||
final String baseUrl;
|
||
final http.Client _client;
|
||
|
||
UpdateChecker({String? baseUrl, http.Client? client})
|
||
: baseUrl = baseUrl ?? AppConfig.apiBaseUrl,
|
||
_client = client ?? http.Client();
|
||
|
||
/// 下载地址:固定静态路径(管理端上传的 APK 覆盖保存为固定文件)
|
||
static String downloadUrl({String? baseUrl}) =>
|
||
'${baseUrl ?? AppConfig.apiBaseUrl}/download/observer-latest.apk';
|
||
|
||
/// 拉取服务器最新版本;非 Android 或网络异常时返回空信息,不阻塞启动
|
||
Future<AppUpdateInfo> fetch() async {
|
||
if (!Platform.isAndroid) {
|
||
return const AppUpdateInfo(version: '', notes: '');
|
||
}
|
||
try {
|
||
final res = await _client
|
||
.get(Uri.parse('$baseUrl/api/v1/app/update'))
|
||
.timeout(const Duration(seconds: 8));
|
||
// 服务器 Content-Type 无 charset,http 包默认按 latin1 解码会乱码 → 显式 utf8
|
||
final body =
|
||
jsonDecode(utf8.decode(res.bodyBytes)) as Map<String, dynamic>;
|
||
final data = body['data'] as Map<String, dynamic>? ?? const {};
|
||
return AppUpdateInfo(
|
||
version: data['version'] as String? ?? '',
|
||
notes: data['notes'] as String? ?? '',
|
||
);
|
||
} catch (_) {
|
||
return const AppUpdateInfo(version: '', notes: '');
|
||
}
|
||
}
|
||
|
||
/// 是否需要更新:服务器版本高于「已装版本与已确认接受版本」中的较大者。
|
||
/// APK 版本号不递增时,用户点过「立即更新」后 accepted 追上服务器版本,
|
||
/// 已更新完成再次启动也不会反复提示。
|
||
static bool needsUpdate(String server, String installed, String accepted) {
|
||
if (server.isEmpty || installed.isEmpty) return false;
|
||
return isNewer(server, installed) || isNewer(server, accepted);
|
||
}
|
||
|
||
/// 语义化版本号比较:a > b 返回 true。按数字段比较(1.10.0 > 1.9.9),
|
||
/// 任一版本号解析失败时视为相等(不触发更新)。
|
||
static bool isNewer(String a, String b) {
|
||
final pa = _parse(a);
|
||
final pb = _parse(b);
|
||
if (pa == null || pb == null) return false;
|
||
for (var i = 0; i < 3; i++) {
|
||
if (pa[i] != pb[i]) return pa[i] > pb[i];
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static List<int>? _parse(String v) {
|
||
final parts = v.split('.');
|
||
if (parts.length != 3) return null;
|
||
final nums = <int>[];
|
||
for (final p in parts) {
|
||
final n = int.tryParse(p);
|
||
if (n == null) return null;
|
||
nums.add(n);
|
||
}
|
||
return nums;
|
||
}
|
||
}
|