9.5 KiB
9.5 KiB
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 用户。
调用流程
- Flutter 调 Google Sign-In / Firebase Auth,拿到 Firebase ID token。
- Flutter 暂存:
provider=firebasecredential=<firebase_id_token>- Google 返回的昵称、头像等基础信息
device_id、platform、language、timezone
- 调注册检查接口。
- 如果
registered=true,调用旧三方登录接口拿 session,写入AppAuthSession后进首页。 - 如果
registered=false,进入资料页,用户补齐username、avatar、gender、country、invite_code。 - 如果用户选择本地头像,先调用注册头像上传接口拿 URL。
- 调
POST /api/v1/auth/third-party/register。 - 成功后把返回的 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。 |
| 格式 | jpg、jpeg、png、webp。 |
| 校验 | 后端按文件头识别图片类型,不信任客户端传入的 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 | 是 | 国家码,例如 SG、CN。 |
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 | 是 | android 或 ios。 |
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 导入接口。