hyapp-server/docs/flutter对接/Google三方注册Flutter对接.md
2026-07-06 11:10:19 +08:00

9.5 KiB
Raw Blame History

Google 三方注册 Flutter 对接

边界

Google 登录后Flutter 只暂存 Firebase ID token 和 Google 基本信息,不写入 AppAuthSession

资料页允许 pending third-party auth 进入,不要求 _authService.isSignedIn。用户点击完成后Flutter 调注册接口;后端校验 Firebase ID token、创建或补齐用户资料、返回 access/refresh token。Flutter 收到 token 后再写入现有 AppAuthSession

旧接口 POST /api/v1/auth/third-party/login 仍保留兼容。新 Google 流程只能在检查接口返回 registered=true 后调用它拿 session不要在 registered=false 时调用它,避免旧接口创建 pending 用户。

调用流程

  1. Flutter 调 Google Sign-In / Firebase Auth拿到 Firebase ID token。
  2. Flutter 暂存:
    • provider=firebase
    • credential=<firebase_id_token>
    • Google 返回的昵称、头像等基础信息
    • device_idplatformlanguagetimezone
  3. 调注册检查接口。
  4. 如果 registered=true,调用旧三方登录接口拿 session写入 AppAuthSession 后进首页。
  5. 如果 registered=false,进入资料页,用户补齐 usernameavatargendercountryinvite_code
  6. 如果用户选择本地头像,先调用注册头像上传接口拿 URL。
  7. POST /api/v1/auth/third-party/register
  8. 成功后把返回的 token 写入 AppAuthSession

注册检查

地址:

POST /api/v1/auth/third-party/registered/check

Headers

Header 必填 说明
Content-Type application/json
X-App-Code App 编码,默认 lalu
X-App-Package 包名;后端可按包名解析 app_code。

参数:

字段 类型 必填 说明
provider string 固定传 firebase
credential string Firebase ID token。

请求示例:

{
  "provider": "firebase",
  "credential": "firebase_id_token"
}

返回值:

{
  "code": 0,
  "message": "ok",
  "request_id": "req_xxx",
  "data": {
    "registered": true
  }
}

Flutter 处理:

registered 处理
true 用户已完成注册;再调用 POST /api/v1/auth/third-party/login 获取 session。
false 用户未完成注册,或者只有旧流程留下的 pending 用户;进入资料页,最后调用注册接口。

注册头像上传

地址:

POST /api/v1/files/registration/avatar/upload

Headers

Header 必填 说明
Content-Type multipart/form-data
X-App-Code App 编码,默认 lalu

参数:

字段 类型 必填 说明
file file 头像文件;也支持字段名 avatar

限制:

说明
登录态 不需要 Authorization
大小 最大 5MB。
格式 jpgjpegpngwebp
校验 后端按文件头识别图片类型,不信任客户端传入的 Content-Type。

返回值:

{
  "code": 0,
  "message": "ok",
  "request_id": "req_xxx",
  "data": {
    "url": "https://media.example.com/app/avatars/registration/20260626/avatar_xxx.png",
    "object_key": "app/avatars/registration/20260626/avatar_xxx.png",
    "content_type": "image/png",
    "size_bytes": 204800
  }
}

Flutter 使用 data.url 作为注册接口的 avatar

三方注册

地址:

POST /api/v1/auth/third-party/register

Headers

Header 必填 说明
Content-Type application/json
X-App-Code App 编码,默认 lalu
X-App-Package 包名;后端可按包名解析 app_code。

参数:

字段 类型 必填 说明
provider string 固定传 firebase
credential string Firebase ID token。
device_id string 设备 ID用于 refresh session 绑定和注册风控。
username string 用户昵称。
avatar string HTTP(S) 头像 URL可以是上传接口返回的 url
gender string 性别值,后端只保存原值。
country string 国家码,例如 SGCN
invite_code string 邀请码。
device string 设备型号。
os_version string 系统版本。
birth string 生日,格式 yyyy-mm-dd
app_version string App 版本。
build_number string 构建号。
source string 注册来源。
install_channel string 安装渠道。
campaign string 投放活动。
platform string androidios
language string 客户端语言,例如 en-US
timezone string IANA 时区,例如 Asia/Shanghai

请求示例:

{
  "provider": "firebase",
  "credential": "firebase_id_token",
  "device_id": "ios_device_001",
  "username": "Alice",
  "avatar": "https://media.example.com/app/avatars/registration/20260626/avatar_xxx.png",
  "gender": "female",
  "country": "SG",
  "invite_code": "",
  "device": "iPhone 15",
  "os_version": "iOS 18.1",
  "birth": "2000-01-02",
  "app_version": "1.2.3",
  "build_number": "123",
  "source": "google",
  "install_channel": "app_store",
  "campaign": "",
  "platform": "ios",
  "language": "en-US",
  "timezone": "Asia/Shanghai"
}

返回值:

{
  "code": 0,
  "message": "ok",
  "request_id": "req_xxx",
  "data": {
    "app_code": "lalu",
    "user_id": "10001",
    "display_user_id": "163001",
    "default_display_user_id": "163001",
    "display_user_id_kind": "default",
    "display_user_id_expires_at_ms": 0,
    "session_id": "sess_xxx",
    "access_token": "access_token",
    "refresh_token": "refresh_token",
    "expires_in_sec": 604800,
    "token_type": "Bearer",
    "is_new_user": true,
    "profile_completed": true,
    "onboarding_status": "completed"
  }
}

Flutter 成功处理:

字段 处理
access_token 写入现有 AppAuthSession.accessToken
refresh_token 写入现有 AppAuthSession.refreshToken
session_id 写入现有 session 标识。
user_id 字符串保存,不要转 JS number。
display_user_id 展示号。
profile_completed 必须为 true;否则不要进入首页。
onboarding_status 成功应为 completed

重试和兼容

同一个 Firebase subject 如果之前已经被旧流程创建成 pending 用户,新注册接口会补齐这个用户并返回 token不会再创建第二个用户。

同一个 Firebase subject 如果已经完成注册,再调该接口会按已有用户登录返回 token不会覆盖用户后续改过的资料。

错误处理

HTTP code 场景 Flutter 处理
400 INVALID_ARGUMENT 缺必填字段、国家格式错误、头像 URL 非 HTTP(S)、生日格式错误。 停留资料页,提示用户修正。
400 INVALID_INVITE_CODE 邀请码格式错误或不可用。 提示邀请码无效,可允许清空后重试。
401 AUTH_FAILED Firebase ID token 无效、过期或 provider 不允许。 回到 Google 登录重新拿 token。
429 RATE_LIMITED 登录注册入口限流。 稍后重试。
409 DEVICE_ALREADY_REGISTERED 当前设备已达到注册账号上限。 提示当前设备无法继续注册。
502 UPSTREAM_ERROR user-service 或对象存储不可用。 稍后重试。

Dart 调用示例

Future<AppAuthSession> registerFirebaseUser({
  required AppNetworkService network,
  required String firebaseIdToken,
  required String deviceId,
  required String username,
  required String avatar,
  required String gender,
  required String country,
  String inviteCode = '',
}) async {
  final envelope = await network.postEnvelope(
    '/api/v1/auth/third-party/register',
    data: {
      'provider': 'firebase',
      'credential': firebaseIdToken,
      'device_id': deviceId,
      'username': username,
      'avatar': avatar,
      'gender': gender,
      'country': country,
      'invite_code': inviteCode,
      'platform': Platform.isIOS ? 'ios' : 'android',
      'language': PlatformDispatcher.instance.locale.toLanguageTag(),
      'timezone': await currentIanaTimezone(),
    },
    requiresAuthorization: false,
  );

  if (envelope.code != 0) {
    throw ApiException(envelope.code, envelope.message);
  }

  final data = envelope.data as Map<String, dynamic>;
  if (data['profile_completed'] != true ||
      data['onboarding_status'] != 'completed') {
    throw StateError('third-party register did not complete profile');
  }

  return AppAuthSession(
    appCode: data['app_code'] as String? ?? '',
    userId: data['user_id'] as String? ?? '',
    displayUserId: data['display_user_id'] as String? ?? '',
    sessionId: data['session_id'] as String? ?? '',
    accessToken: data['access_token'] as String? ?? '',
    refreshToken: data['refresh_token'] as String? ?? '',
    expiresInSec: (data['expires_in_sec'] as num? ?? 0).toInt(),
    tokenType: data['token_type'] as String? ?? 'Bearer',
    profileCompleted: data['profile_completed'] == true,
    onboardingStatus: data['onboarding_status'] as String? ?? '',
  );
}

相关 IM注册接口成功后后端会导入腾讯 IM 用户资料Flutter 不需要额外调用 IM 导入接口。