This commit is contained in:
Developer
2026-06-06 06:12:30 +08:00
parent 214a0684d0
commit bc7cb075c5
9 changed files with 8 additions and 712 deletions

View File

@@ -2,12 +2,10 @@
/// 闲言APP — 登录页面
/// 创建时间: 2026-04-28
/// 更新时间: 2026-06-05
/// 作用: 用户登录界面,支持密码/验证码/Token/老用户/二维码/OAuth社交登录
/// 上次更新: 新增Apple/Google/GitHub OAuth社交登录入口
/// 作用: 用户登录界面,支持密码/验证码/Token/老用户/二维码登录
/// 上次更新: 移除OAuth社交登录(未正式使用)
/// ============================================================
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
@@ -17,7 +15,6 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../../../core/router/app_routes.dart';
import '../../../core/network/api_exception.dart';
import '../../../core/services/device/device_info_service.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/theme/app_spacing.dart';
import '../../../core/theme/app_typography.dart';
@@ -31,7 +28,6 @@ import '../config/register_config.dart';
import '../providers/auth_provider.dart';
import '../services/auth_service.dart';
import '../services/email_service.dart';
import '../services/oauth_service.dart';
import 'login_form_sections.dart';
import 'register_section.dart';
@@ -527,60 +523,6 @@ class _LoginPageState extends ConsumerState<LoginPage>
],
),
const SizedBox(height: AppSpacing.md),
// TODO: [OAuth社交登录] 待各平台OAuth配置完成后启用详见 docs/OAUTH_INTEGRATION_GUIDE.md
// 第二行分隔线:社交登录
// Row(
// children: [
// Expanded(
// child: Divider(color: ext.textHint.withValues(alpha: 0.15)),
// ),
// Padding(
// padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm),
// child: Text(
// auth.otherLoginMethods,
// style: AppTypography.caption2.copyWith(
// color: ext.textHint,
// fontSize: 10,
// ),
// ),
// ),
// Expanded(
// child: Divider(color: ext.textHint.withValues(alpha: 0.15)),
// ),
// ],
// ),
// const SizedBox(height: AppSpacing.md),
// // 第三行Apple/Google/GitHub
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// // Apple登录仅iOS/macOS
// if (Platform.isIOS || Platform.isMacOS) ...[
// _buildOAuthButton(
// ext,
// icon: '',
// label: 'Apple',
// onTap: () => _handleOAuthLogin(OAuthPlatform.apple),
// ),
// const SizedBox(width: AppSpacing.lg),
// ],
// // Google登录
// _buildOAuthButton(
// ext,
// icon: '',
// label: 'Google',
// onTap: () => _handleOAuthLogin(OAuthPlatform.google),
// ),
// const SizedBox(width: AppSpacing.lg),
// // GitHub登录
// _buildOAuthButton(
// ext,
// icon: '',
// label: 'GitHub',
// onTap: () => _handleOAuthLogin(OAuthPlatform.github),
// ),
// ],
// ),
],
);
}
@@ -654,96 +596,6 @@ class _LoginPageState extends ConsumerState<LoginPage>
);
}
/// OAuth社交登录按钮
Widget _buildOAuthButton(
AppThemeExtension ext, {
required String icon,
required String label,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Column(
children: [
Container(
width: 52,
height: 52,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: ext.bgCard,
border: Border.all(
color: ext.textHint.withValues(alpha: 0.2),
),
),
child: Center(
child: Text(icon, style: const TextStyle(fontSize: 22)),
),
),
const SizedBox(height: 4),
Text(
label,
style: AppTypography.caption2.copyWith(
color: ext.textSecondary,
fontSize: 10,
),
),
],
),
);
}
/// 处理OAuth社交登录
Future<void> _handleOAuthLogin(OAuthPlatform platform) async {
final deviceModel = DeviceInfoService.cachedDeviceModel;
final deviceName = DeviceInfoService.cachedDeviceName;
OAuthLoginResult result;
switch (platform) {
case OAuthPlatform.apple:
result = await OAuthService.loginWithApple(
deviceName: deviceName,
deviceModel: deviceModel,
platformType: Platform.isIOS ? 'ios' : 'mac',
);
case OAuthPlatform.google:
result = await OAuthService.loginWithGoogle(
deviceName: deviceName,
deviceModel: deviceModel,
platformType: Platform.isIOS ? 'ios' : 'android',
);
case OAuthPlatform.github:
result = await OAuthService.loginWithGithub(
deviceName: deviceName,
deviceModel: deviceModel,
platformType: Platform.isIOS ? 'ios' : 'android',
);
}
if (!mounted) return;
if (result.success && result.token != null) {
// 登录成功通过token登录到本地
try {
await AuthService.tokenLogin(result.token!);
ref.invalidate(authProvider);
if (mounted) {
final t = ref.read(translationsProvider);
AppToast.showSuccess(t.auth.loginSuccess);
_navigateAfterLogin();
}
} on ApiException catch (e) {
if (mounted) AppToast.showError(e.message);
} catch (e) {
if (mounted) AppToast.showError('登录失败: $e');
}
} else if (result.error != null) {
if (result.error != '用户取消') {
AppToast.showError(result.error!);
}
}
}
Widget _buildAgreement(AppThemeExtension ext, TAuth auth) {
return Padding(
padding: const EdgeInsets.only(top: AppSpacing.md),

View File

@@ -1,377 +0,0 @@
/// ============================================================
/// 文件: oauth_service.dart
/// 创建时间: 2026-06-05
/// 更新时间: 2026-06-05
/// 名称: OAuth社交登录服务
/// 作用: 处理Apple/Google/GitHub第三方登录
/// 上次更新: 新增OAuth社交登录服务
/// ============================================================
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_web_auth/flutter_web_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import '../../../core/network/api_client.dart';
import '../../../core/utils/logger.dart';
import '../../../core/utils/platform/platform_utils.dart' as pu;
/// OAuth平台枚举
enum OAuthPlatform {
apple,
google,
github;
/// 平台标识值
String get value => switch (this) {
OAuthPlatform.apple => 'apple',
OAuthPlatform.google => 'google',
OAuthPlatform.github => 'github',
};
/// 显示名称
String get displayName => switch (this) {
OAuthPlatform.apple => 'Apple',
OAuthPlatform.google => 'Google',
OAuthPlatform.github => 'GitHub',
};
/// 图标emoji
String get icon => switch (this) {
OAuthPlatform.apple => '',
OAuthPlatform.google => '',
OAuthPlatform.github => '',
};
}
/// OAuth登录结果
class OAuthLoginResult {
const OAuthLoginResult({
required this.success,
this.token,
this.userinfo,
this.isNewUser = false,
this.bindPlatform,
this.error,
});
final bool success;
final String? token;
final Map<String, dynamic>? userinfo;
final bool isNewUser;
final String? bindPlatform;
final String? error;
/// 从服务端响应JSON构造
factory OAuthLoginResult.fromJson(Map<String, dynamic> json) {
final data = json['data'];
final Map<String, dynamic>? dataMap =
data is Map<String, dynamic> ? data : null;
return OAuthLoginResult(
success: json['code'] == 1,
token: dataMap?['token'] as String?,
userinfo: dataMap?['userinfo'] != null
? Map<String, dynamic>.from(dataMap!['userinfo'] as Map)
: null,
isNewUser: dataMap?['is_new_user'] as bool? ?? false,
bindPlatform: dataMap?['bind_platform'] as String?,
error: json['code'] != 1 ? json['msg'] as String? : null,
);
}
}
/// OAuth社交登录服务
class OAuthService {
OAuthService._();
static const String _tag = 'OAuthService';
/// ApiClient单例的Dio实例
static final Dio _dio = ApiClient.instance.dio;
// ============================================================
// Apple登录
// ============================================================
/// Apple登录
///
/// 仅支持iOS和macOS平台通过Sign in with Apple获取id_token后发送到服务端验证。
static Future<OAuthLoginResult> loginWithApple({
String? deviceName,
String? deviceModel,
String? platformType,
String? deviceId,
}) async {
try {
if (!pu.isIOS && !pu.isMacOS) {
return const OAuthLoginResult(
success: false,
error: 'Apple登录仅支持iOS和macOS',
);
}
final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
webAuthenticationOptions: pu.isIOS
? null
: WebAuthenticationOptions(
clientId: 'apps.xy.xianyan.service',
redirectUri:
Uri.parse('https://tools.wktyl.com/oauth/callback'),
),
);
Log.i('$_tag: Apple登录获取id_token成功');
return _loginToServer(
platform: OAuthPlatform.apple,
idToken: credential.identityToken,
authorizationCode: credential.authorizationCode,
deviceName: deviceName,
deviceModel: deviceModel,
platformType: platformType,
deviceId: deviceId,
);
} on SignInWithAppleAuthorizationException catch (e) {
if (e.code == AuthorizationErrorCode.canceled) {
return const OAuthLoginResult(success: false, error: '用户取消');
}
Log.e('$_tag: Apple登录失败: $e');
return OAuthLoginResult(
success: false,
error: 'Apple登录失败: ${e.message}',
);
} on PlatformException catch (e) {
Log.e('$_tag: Apple登录平台异常: $e');
return const OAuthLoginResult(success: false, error: 'Apple登录失败');
} catch (e) {
Log.e('$_tag: Apple登录异常: $e');
return const OAuthLoginResult(success: false, error: 'Apple登录失败');
}
}
// ============================================================
// Google登录
// ============================================================
/// Google登录
///
/// 通过Google Sign In获取serverAuthCode后发送到服务端验证。
static Future<OAuthLoginResult> loginWithGoogle({
String? deviceName,
String? deviceModel,
String? platformType,
String? deviceId,
}) async {
try {
final googleSignIn = GoogleSignIn(scopes: ['email', 'profile']);
final account = await googleSignIn.signIn();
if (account == null) {
return const OAuthLoginResult(success: false, error: '用户取消');
}
final auth = await account.authentication;
final serverAuthCode = auth.serverAuthCode;
if (serverAuthCode == null) {
Log.w('$_tag: Google serverAuthCode为空尝试使用idToken');
return const OAuthLoginResult(
success: false,
error: 'Google授权码获取失败请重试',
);
}
Log.i('$_tag: Google登录获取serverAuthCode成功');
return _loginToServer(
platform: OAuthPlatform.google,
code: serverAuthCode,
deviceName: deviceName,
deviceModel: deviceModel,
platformType: platformType,
deviceId: deviceId,
);
} on PlatformException catch (e) {
if (e.code == 'sign_in_canceled') {
return const OAuthLoginResult(success: false, error: '用户取消');
}
Log.e('$_tag: Google登录平台异常: $e');
return const OAuthLoginResult(success: false, error: 'Google登录失败');
} catch (e) {
Log.e('$_tag: Google登录异常: $e');
return const OAuthLoginResult(success: false, error: 'Google登录失败');
}
}
// ============================================================
// GitHub登录
// ============================================================
/// GitHub登录通过浏览器OAuth
///
/// 先从服务端获取OAuth配置再通过浏览器完成授权流程最后将授权码发送到服务端。
static Future<OAuthLoginResult> loginWithGithub({
String? deviceName,
String? deviceModel,
String? platformType,
String? deviceId,
}) async {
try {
// 从服务端获取GitHub OAuth配置
final configResp = await _dio.get<Map<String, dynamic>>(
'/api/oauth/config',
queryParameters: {'platform': 'github'},
);
final configData = configResp.data;
if (configData == null ||
configData['code'] != 1 ||
(configData['data'] as Map<String, dynamic>?)?['configured'] !=
true) {
return const OAuthLoginResult(
success: false,
error: 'GitHub登录暂未配置',
);
}
final dataMap = configData['data'] as Map<String, dynamic>;
final authorizeUrl = dataMap['authorize_url'] as String;
// 通过浏览器完成OAuth
final result = await FlutterWebAuth.authenticate(
url: authorizeUrl,
callbackUrlScheme: 'xianyan',
);
// 从回调URL中提取code
final callbackUri = Uri.parse(result);
final code = callbackUri.queryParameters['code'];
if (code == null || code.isEmpty) {
return const OAuthLoginResult(
success: false,
error: 'GitHub授权码获取失败',
);
}
Log.i('$_tag: GitHub登录获取授权码成功');
return _loginToServer(
platform: OAuthPlatform.github,
code: code,
deviceName: deviceName,
deviceModel: deviceModel,
platformType: platformType,
deviceId: deviceId,
);
} on PlatformException catch (e) {
if (e.code == 'CANCELED') {
return const OAuthLoginResult(success: false, error: '用户取消');
}
Log.e('$_tag: GitHub登录平台异常: $e');
return const OAuthLoginResult(success: false, error: 'GitHub登录失败');
} catch (e) {
Log.e('$_tag: GitHub登录异常: $e');
return const OAuthLoginResult(success: false, error: 'GitHub登录失败');
}
}
// ============================================================
// 绑定/解绑社交账号
// ============================================================
/// 绑定社交账号
static Future<Map<String, dynamic>> bind({
required OAuthPlatform platform,
String? code,
String? idToken,
}) async {
final resp = await _dio.post<Map<String, dynamic>>(
'/api/oauth/bind',
data: {
'platform': platform.value,
if (code != null) 'code': code,
if (idToken != null) 'id_token': idToken,
},
);
return resp.data ?? {};
}
/// 解绑社交账号
static Future<Map<String, dynamic>> unbind({
required OAuthPlatform platform,
}) async {
final resp = await _dio.post<Map<String, dynamic>>(
'/api/oauth/unbind',
data: {'platform': platform.value},
);
return resp.data ?? {};
}
/// 获取已绑定列表
static Future<List<Map<String, dynamic>>> getBoundList() async {
final resp = await _dio.get<Map<String, dynamic>>('/api/oauth/bound');
final data = resp.data;
if (data != null && data['code'] == 1) {
final innerData = data['data'];
if (innerData is Map<String, dynamic> &&
innerData['bindings'] != null) {
return List<Map<String, dynamic>>.from(
innerData['bindings'] as List,
);
}
}
return [];
}
// ============================================================
// 内部方法
// ============================================================
/// 向服务端发送登录请求
static Future<OAuthLoginResult> _loginToServer({
required OAuthPlatform platform,
String? code,
String? idToken,
String? authorizationCode,
String? deviceName,
String? deviceModel,
String? platformType,
String? deviceId,
}) async {
try {
final resp = await _dio.post<Map<String, dynamic>>(
'/api/oauth/login',
data: {
'platform': platform.value,
if (code != null) 'code': code,
if (idToken != null) 'id_token': idToken,
if (authorizationCode != null) 'code': authorizationCode,
if (deviceName != null) 'device_name': deviceName,
if (deviceModel != null) 'device_model': deviceModel,
if (platformType != null) 'platform_type': platformType,
if (deviceId != null) 'device_id': deviceId,
},
);
final result = OAuthLoginResult.fromJson(resp.data ?? {});
Log.i(
'$_tag: ${platform.displayName}登录${result.success ? "成功" : "失败"}',
);
return result;
} on DioException catch (e) {
Log.e('$_tag: 服务端登录请求失败: $e');
return const OAuthLoginResult(
success: false,
error: '网络请求失败,请检查网络连接',
);
} catch (e) {
Log.e('$_tag: 服务端登录异常: $e');
return const OAuthLoginResult(success: false, error: '登录失败');
}
}
}