fix:参考金值,研听,研值工程通用通用工程公共能力,重点对比网络请求、登录认证、状态管理、本地存储、WebView、推送、版本更新、一键登录、主题/UI 等模块,确定后续统一整改方案

This commit is contained in:
jingyun committed 2026-09-10 09:52:36 +08:00
1 parent 6997a65c96
commit d364c6d908
46 files changed
+2060 -395

No files matched your search

+32 -61
View File
@@ -1,12 +1,12 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../network/dio_provider.dart';
import '../../models/pb/jinzhi/v1/push.pb.dart' as pb;
import '../network/api_exception.dart';
import 'notification_permission.dart';
import 'push_api_client.dart';
import 'umeng_push_service.dart';
class DevicePushRegistrar {
@@ -15,81 +15,52 @@ class DevicePushRegistrar {
StreamSubscription<String>? _subscription;
String? _lastToken;
bool? _lastPermission;
int _attempt = 0;
Future<void> _registrationQueue = Future.value();
void start() {
_subscription ??= UmengPushService.onDeviceToken.listen(_register);
_subscription ??= UmengPushService.onDeviceToken.listen(_enqueue);
UmengPushService.emitCachedToken();
}
Future<void> stop() async => _subscription?.cancel();
Future<void> refresh() => UmengPushService.refreshDeviceToken();
void forceRegisterCurrentToken() {
// BLOCKED(server contract): guest registration is intentionally preserved.
// Do not add a client-side unbind call until the backend confirms whether
// guest registration reliably clears the previous user binding.
_lastToken = null;
_lastPermission = null;
UmengPushService.emitCachedToken();
}
void _enqueue(String token) {
_registrationQueue = _registrationQueue
.catchError((_) {})
.then((_) => _register(token));
}
Future<void> _register(String token) async {
final permission = await NotificationPermission.isGranted();
if (token == _lastToken && permission == _lastPermission) return;
try {
final package = await PackageInfo.fromPlatform();
await _read(dioProvider).post<List<int>>(
'/api/v1/push/devices',
data: _PushRegisterRequest(
token: token,
platform: defaultTargetPlatform == TargetPlatform.iOS ? 1 : 2,
version: package.version,
permission: permission,
).toBytes(),
options: protobufOptions(),
);
_lastToken = token;
_lastPermission = permission;
_attempt = 0;
} catch (_) {
if (_attempt++ >= 3) return;
await Future<void>.delayed(Duration(seconds: 1 << (_attempt - 1)));
await _register(token);
}
}
}
class _PushRegisterRequest {
const _PushRegisterRequest({
required this.token,
required this.platform,
required this.version,
required this.permission,
});
final String token;
final int platform;
final String version;
final bool permission;
Uint8List toBytes() {
final bytes = BytesBuilder(copy: false);
void putInt(int value) {
while (value > 0x7f) {
bytes.addByte((value & 0x7f) | 0x80);
value >>= 7;
final package = await PackageInfo.fromPlatform();
final request = pb.RegisterPushDeviceRequest(
umengDeviceToken: token,
platform: defaultTargetPlatform == TargetPlatform.iOS
? pb.Platform.PLATFORM_IOS
: pb.Platform.PLATFORM_ANDROID,
appVersion: package.version,
permissionGranted: permission,
);
for (var attempt = 0; attempt < 4; attempt++) {
try {
await _read(pushApiClientProvider).registerDevice(request);
_lastToken = token;
_lastPermission = permission;
return;
} catch (error) {
if (attempt == 3 || !ApiException.from(error).isRetryable) return;
await Future<void>.delayed(Duration(seconds: 1 << attempt));
}
bytes.addByte(value);
}
void putString(int tag, String value) {
final raw = value.codeUnits;
putInt((tag << 3) | 2);
putInt(raw.length);
bytes.add(raw);
}
putString(1, token);
putInt(2 << 3);
putInt(platform);
putString(3, version);
putInt(4 << 3);
putInt(permission ? 1 : 0);
return bytes.toBytes();
}
}
+24
View File
@@ -0,0 +1,24 @@
import 'package:dio/dio.dart';
// ignore: unused_import — compute() is referenced by generated Retrofit code.
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:retrofit/retrofit.dart';
import '../../models/pb/jinzhi/v1/push.pb.dart';
import '../network/dio_provider.dart';
part 'push_api_client.g.dart';
@RestApi()
abstract class PushApiClient {
factory PushApiClient(Dio dio, {String? baseUrl}) = _PushApiClient;
@POST('/api/v1/push/devices')
Future<RegisterPushDeviceReply> registerDevice(
@Body() RegisterPushDeviceRequest request,
);
}
final pushApiClientProvider = Provider<PushApiClient>(
(ref) => PushApiClient(ref.watch(backendDioProvider)),
);
+10 -6
View File
@@ -12,11 +12,12 @@ import '../storage/storage_keys.dart';
import '../storage/storage_service.dart';
abstract final class UmengPushService {
static const _maxPendingNotificationOpens = 20;
static final _tokens = StreamController<String>.broadcast();
static Future<void>? _starting;
static bool _started = false;
static void Function(String payload)? _onNotificationOpen;
static String? _pendingNotificationOpen;
static final List<String> _pendingNotificationOpens = [];
static Stream<String> get onDeviceToken => _tokens.stream;
@@ -24,10 +25,10 @@ abstract final class UmengPushService {
void Function(String payload) callback,
) {
_onNotificationOpen = callback;
final pending = _pendingNotificationOpen;
if (pending != null) {
_pendingNotificationOpen = null;
callback(pending);
final pending = List<String>.of(_pendingNotificationOpens);
_pendingNotificationOpens.clear();
for (final payload in pending) {
callback(payload);
}
}
@@ -56,7 +57,10 @@ abstract final class UmengPushService {
UmengPushSdk.setNotificationCallback((_) {}, (payload) {
final callback = _onNotificationOpen;
if (callback == null) {
_pendingNotificationOpen = payload;
if (_pendingNotificationOpens.length == _maxPendingNotificationOpens) {
_pendingNotificationOpens.removeAt(0);
}
_pendingNotificationOpens.add(payload);
} else {
callback(payload);
}