Files
jinzhi/lib/features/auth/data/auth_api.dart
T
jingyun 3b089e2205 feat: 对齐常驻商城与七鱼客服交互
- 同步商城 H5 登录态与原生 token
- 完善商城/原生 tab 切换、动画及返回交互
- 修复七鱼客服键盘避让和状态栏恢复
- 调整开屏跳过按钮与登录入口交互
2026-09-07 15:47:01 +08:00

138 lines
3.8 KiB
Dart

import 'dart:convert';
import 'package:dio/dio.dart';
import '../../../common/network/dio_provider.dart';
/// 金值账号服务的 protobuf HTTP 契约。
///
/// 这个轻量编解码只覆盖短信登录需要的字段,避免把整套历史生成模型带入
/// 当前重建中的应用;字段号与金值 `account.proto` 保持一致。
class AuthApi {
const AuthApi(this._dio);
final Dio _dio;
Future<int> sendSmsCode(String phone) async {
final response = await _dio.post<List<int>>(
'/api/v1/auth/sms/send',
data: _ProtoWriter.stringField(1, phone),
options: protobufOptions(),
);
return _ProtoMessage(response.data ?? const <int>[]).integer(1);
}
Future<AuthLoginResult> verifySmsCode(String phone, String code) async {
final response = await _dio.post<List<int>>(
'/api/v1/auth/sms/verify',
data: [
..._ProtoWriter.stringField(1, phone),
..._ProtoWriter.stringField(2, code),
],
options: protobufOptions(),
);
final reply = _ProtoMessage(response.data ?? const <int>[]);
final accessToken = reply.string(1).trim();
if (accessToken.isEmpty) {
throw const FormatException('登录响应缺少 access_token');
}
final user = _ProtoMessage(reply.bytes(3));
return AuthLoginResult(
accessToken: accessToken,
phone: user.string(2).trim().isEmpty ? phone : user.string(2).trim(),
);
}
}
class AuthLoginResult {
const AuthLoginResult({required this.accessToken, required this.phone});
final String accessToken;
final String phone;
}
class _ProtoWriter {
static List<int> stringField(int field, String value) => [
..._varint(field << 3 | 2),
..._varint(utf8.encode(value).length),
...utf8.encode(value),
];
static List<int> _varint(int value) {
final bytes = <int>[];
var remaining = value;
while (remaining > 0x7f) {
bytes.add(remaining & 0x7f | 0x80);
remaining >>= 7;
}
bytes.add(remaining);
return bytes;
}
}
class _ProtoMessage {
_ProtoMessage(List<int> bytes) : _fields = _parse(bytes);
final Map<int, Object> _fields;
int integer(int field) => _fields[field] is int ? _fields[field]! as int : 0;
String string(int field) => _fields[field] is List<int>
? utf8.decode(_fields[field]! as List<int>, allowMalformed: true)
: '';
List<int> bytes(int field) => _fields[field] is List<int>
? _fields[field]! as List<int>
: const <int>[];
static Map<int, Object> _parse(List<int> bytes) {
final fields = <int, Object>{};
var cursor = 0;
while (cursor < bytes.length) {
final tag = _readVarint(bytes, cursor);
if (tag == null) break;
cursor = tag.next;
final field = tag.value >> 3;
switch (tag.value & 7) {
case 0:
final value = _readVarint(bytes, cursor);
if (value == null) return fields;
fields[field] = value.value;
cursor = value.next;
case 2:
final length = _readVarint(bytes, cursor);
if (length == null || length.next + length.value > bytes.length) {
return fields;
}
fields[field] = bytes.sublist(
length.next,
length.next + length.value,
);
cursor = length.next + length.value;
default:
return fields;
}
}
return fields;
}
static _ProtoValue? _readVarint(List<int> bytes, int cursor) {
var value = 0;
var shift = 0;
while (cursor < bytes.length && shift <= 63) {
final byte = bytes[cursor++];
value |= (byte & 0x7f) << shift;
if (byte & 0x80 == 0) return _ProtoValue(value, cursor);
shift += 7;
}
return null;
}
}
class _ProtoValue {
const _ProtoValue(this.value, this.next);
final int value;
final int next;
}