import 'dart:async'; import 'dart:typed_data'; import 'package:common_pub/model/game_map.dart'; import 'package:get/get.dart'; import 'package:grpc/grpc.dart'; import '../logger.dart'; import 'app.dart'; import '../global_var.dart'; import 'package:common_pub/pb.dart' as pb; export 'package:common_pub/pb.dart'; typedef SmsType = pb.SmsType; class ApiService extends GetxService{ static ApiService get to => Get.find(); AppService get app => AppService.to; ClientChannel? channel; String get _appVersion => app.appVersion; Future init()async{ channel = _newChannel(); await syncTime(); return this; } String? get token { final out = app.userProfile.token.val; if (out.isEmpty) { return null; } return out; } set token(String? v) { app.userProfile.token.val = v ?? ''; } ClientChannel _newChannel(){ return ClientChannel( GlobalVar.apiHost, port: GlobalVar.apiPort, options: const ChannelOptions(credentials: ChannelCredentials.secure() // ChannelCredentials.insecure() ), ); } pb.ApiToAppClient _newStub({Duration? timeout, ClientChannel? channel}){ if (this.channel == null) { throw Exception('$runtimeType 未初始化'); } final metadata = { 'source': "${pb.LoginSource.ToApp.value}" }; metadata['version'] = _appVersion; if (token != null) { metadata['token'] = token!; } // debug("token: $token"); return pb.ApiToAppClient(channel??this.channel!, options: CallOptions( metadata: metadata, timeout: timeout, )); } pb.ApiToAppClient get stub { return _newStub(timeout: 10.seconds); } // 获取系统时间 Future serverTime() async { final begin = DateTime.now(); final r = await stub.toGetServerTime(pb.DefaultRequest()); final cost = DateTime.now().difference(begin); final serverNow = DateTime.fromMillisecondsSinceEpoch( r.millisecondStamp.toInt(), isUtc: true) .toLocal(); return serverNow.add(cost); } Future syncTime()async{ try { final serverNow = await serverTime(); app.correctByServerNow(serverNow); info('服务器时间:${app.now}'); } catch(e){ warn("获取服务器时间失败: ", e); } } // 获取短信验证码 Future authSendCodeToPhone(String phone, SmsType smsType)async{ info('authSendCodeToPhone [$phone]'); await stub.toSendCodeToPhoneV2(pb.ToSendCodeToPhoneRequestV2() ..phone= phone ..smsType= smsType ); } // 场控端_登录 Future signIn(String userCode, String password, String ip) async { final r = await stub.toSignInV2(pb.ToSignInRequestV2() ..userCode = userCode ..password = password ..ip = ip ); token = r.token; debug('sign in success: $token'); } // 场控端_登出 void signOut(){ stub.toSignOutV2(pb.DefaultRequest()); token = null; } Future getSmsSendLeftTime(String phone)async{ final r = await stub.toGetSmsSendLeftTimeV2(pb.GetSmsSendLeftTimeRequest()..phone= phone); info('getSmsSendLeftTime: $phone - ${r.second}s'); return r.second.seconds; } Future getBinReaderByMd5(Uint8List md5) async { final stream = stub.toGetBinaryByMd5(pb.ToGetBinaryByMd5Request()..md5=md5); final controller = StreamController>(); controller.onCancel = (){ stream.cancel(); }; Future rcv()async{ try{ await for(final one in stream){ controller.add(one.data); } }finally{ controller.close(); stream.cancel(); } } rcv(); stream.headers.then((value) => debug(value)); final headers = await stream.headers; final lenStr = headers['all-length']!; final length = int.parse(lenStr); final nonce = headers['nonce']!; final ext = headers['ext']!; return BinReader( data: controller.stream, length: length, ext: ext, nonce: nonce ); } } extension NetImageExt on pb.NetImage{ GameMap toGameMap(){ final md5Data = Uint8List.fromList(md5); return GameMap() ..url = url ..md5 = md5Data ..readerBuilder = ()=> ApiService.to.getBinReaderByMd5(md5Data); } }