refactor: 移除前端 Mock API,新增共享组件,优化认证流程
- 删除 mock_api_client、mock_calendar_service、mock_history_service - 新增 fixed_length_code_input、link_button、message_composer 共享组件 - 优化登录/注册/密码重置页面使用新组件 - 简化 injection.dart 移除 mock 分支 - 更新 env.dart 配置(BACKEND_URL 替换 API_URL) - 后端 agentscope 工具和测试更新 - 重构 AGENTS.md 文档结构 - 新增 deploy/ 目录和 protocol 文档
This commit is contained in:
@@ -1,181 +0,0 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'i_api_client.dart';
|
||||
|
||||
class MockRequest {
|
||||
final String path;
|
||||
final String method;
|
||||
final dynamic data;
|
||||
final Options? options;
|
||||
final Map<String, String>? headers;
|
||||
|
||||
MockRequest({
|
||||
required this.path,
|
||||
required this.method,
|
||||
this.data,
|
||||
this.options,
|
||||
this.headers,
|
||||
});
|
||||
}
|
||||
|
||||
typedef MockHandler = dynamic Function(MockRequest request);
|
||||
|
||||
class _PatternRoute {
|
||||
final RegExp pattern;
|
||||
final String method;
|
||||
final MockHandler handler;
|
||||
|
||||
_PatternRoute({
|
||||
required this.pattern,
|
||||
required this.method,
|
||||
required this.handler,
|
||||
});
|
||||
}
|
||||
|
||||
class MockApiClient implements IApiClient {
|
||||
final Map<String, MockHandler> _handlers = {};
|
||||
final List<_PatternRoute> _patternHandlers = [];
|
||||
|
||||
void registerHandler(String path, String method, MockHandler handler) {
|
||||
final key = '$path:$method';
|
||||
_handlers[key] = handler;
|
||||
}
|
||||
|
||||
void registerPatternHandler(
|
||||
RegExp pattern,
|
||||
String method,
|
||||
MockHandler handler,
|
||||
) {
|
||||
_patternHandlers.add(
|
||||
_PatternRoute(
|
||||
pattern: pattern,
|
||||
method: method.toUpperCase(),
|
||||
handler: handler,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void clearMocks() {
|
||||
_handlers.clear();
|
||||
_patternHandlers.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<T>> get<T>(String path, {Options? options}) async {
|
||||
return _handleRequest('GET', path, options: options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<T>> post<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Options? options,
|
||||
}) async {
|
||||
return _handleRequest('POST', path, data: data, options: options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<T>> patch<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Options? options,
|
||||
}) async {
|
||||
return _handleRequest('PATCH', path, data: data, options: options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<T>> delete<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Options? options,
|
||||
}) async {
|
||||
return _handleRequest('DELETE', path, data: data, options: options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Stream<String>> getSseLines(
|
||||
String path, {
|
||||
Map<String, String>? headers,
|
||||
}) async {
|
||||
final key = '$path:SSE';
|
||||
final direct = _handlers[key];
|
||||
if (direct != null) {
|
||||
final response = direct(
|
||||
MockRequest(path: path, method: 'SSE', headers: headers),
|
||||
);
|
||||
if (response is Stream<String>) {
|
||||
return response;
|
||||
}
|
||||
if (response is Iterable<String>) {
|
||||
return Stream<String>.fromIterable(response);
|
||||
}
|
||||
return const Stream<String>.empty();
|
||||
}
|
||||
for (final route in _patternHandlers) {
|
||||
if (route.method != 'SSE') {
|
||||
continue;
|
||||
}
|
||||
if (!route.pattern.hasMatch(path)) {
|
||||
continue;
|
||||
}
|
||||
final response = route.handler(
|
||||
MockRequest(path: path, method: 'SSE', headers: headers),
|
||||
);
|
||||
if (response is Stream<String>) {
|
||||
return response;
|
||||
}
|
||||
if (response is Iterable<String>) {
|
||||
return Stream<String>.fromIterable(response);
|
||||
}
|
||||
return const Stream<String>.empty();
|
||||
}
|
||||
return const Stream<String>.empty();
|
||||
}
|
||||
|
||||
Future<Response<T>> _handleRequest<T>(
|
||||
String method,
|
||||
String path, {
|
||||
dynamic data,
|
||||
Options? options,
|
||||
}) async {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
final handler = _resolveHandler(path: path, method: method);
|
||||
|
||||
if (handler != null) {
|
||||
final response = handler(
|
||||
MockRequest(path: path, method: method, data: data, options: options),
|
||||
);
|
||||
if (response is Response) {
|
||||
return response as Response<T>;
|
||||
}
|
||||
return Response<T>(
|
||||
data: response as T?,
|
||||
statusCode: 200,
|
||||
requestOptions: RequestOptions(path: path),
|
||||
);
|
||||
}
|
||||
|
||||
return Response<T>(
|
||||
data: null,
|
||||
statusCode: 404,
|
||||
requestOptions: RequestOptions(path: path),
|
||||
);
|
||||
}
|
||||
|
||||
MockHandler? _resolveHandler({required String path, required String method}) {
|
||||
final key = '$path:$method';
|
||||
final direct = _handlers[key];
|
||||
if (direct != null) {
|
||||
return direct;
|
||||
}
|
||||
for (final route in _patternHandlers) {
|
||||
if (route.method != method.toUpperCase()) {
|
||||
continue;
|
||||
}
|
||||
if (route.pattern.hasMatch(path)) {
|
||||
return route.handler;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@ import 'dart:io';
|
||||
|
||||
class Env {
|
||||
static String get apiUrl {
|
||||
const url = String.fromEnvironment('API_URL');
|
||||
if (url.isNotEmpty) return url;
|
||||
final backendUrl = const String.fromEnvironment('BACKEND_URL');
|
||||
if (backendUrl.isNotEmpty && backendUrl != 'false') {
|
||||
return backendUrl;
|
||||
}
|
||||
if (Platform.isAndroid) {
|
||||
return 'http://192.168.1.25:5775';
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import '../api/api_client.dart';
|
||||
import '../api/i_api_client.dart';
|
||||
import '../api/mock_api_client.dart';
|
||||
import '../storage/token_storage.dart';
|
||||
import '../config/env.dart';
|
||||
import '../notifications/local_notification_service.dart';
|
||||
@@ -12,7 +11,7 @@ import '../../features/auth/data/auth_repository.dart';
|
||||
import '../../features/auth/data/auth_repository_impl.dart';
|
||||
import '../../features/auth/presentation/bloc/auth_bloc.dart';
|
||||
import '../../features/calendar/data/calendar_api.dart';
|
||||
import '../../features/calendar/data/services/mock_calendar_service.dart';
|
||||
import '../../features/calendar/data/services/calendar_service.dart';
|
||||
import '../../features/calendar/ui/calendar_state_manager.dart';
|
||||
import '../../features/friends/data/friends_api.dart';
|
||||
import '../../features/messages/data/inbox_api.dart';
|
||||
@@ -29,18 +28,13 @@ Future<void> configureDependencies() async {
|
||||
final IApiClient apiClient;
|
||||
final SecureTokenStorage tokenStorage;
|
||||
|
||||
if (Env.isMockApi) {
|
||||
apiClient = MockApiClient();
|
||||
tokenStorage = SecureTokenStorage(const FlutterSecureStorage());
|
||||
} else {
|
||||
final dio = Dio(BaseOptions(baseUrl: Env.apiUrl));
|
||||
tokenStorage = SecureTokenStorage(const FlutterSecureStorage());
|
||||
apiClient = ApiClient(
|
||||
baseUrl: Env.apiUrl,
|
||||
tokenStorage: tokenStorage,
|
||||
dio: dio,
|
||||
);
|
||||
}
|
||||
final dio = Dio(BaseOptions(baseUrl: Env.apiUrl));
|
||||
tokenStorage = SecureTokenStorage(const FlutterSecureStorage());
|
||||
apiClient = ApiClient(
|
||||
baseUrl: Env.apiUrl,
|
||||
tokenStorage: tokenStorage,
|
||||
dio: dio,
|
||||
);
|
||||
|
||||
sl.registerSingleton<IApiClient>(apiClient);
|
||||
|
||||
@@ -53,9 +47,7 @@ Future<void> configureDependencies() async {
|
||||
final calendarApi = CalendarApi(apiClient);
|
||||
sl.registerSingleton<CalendarApi>(calendarApi);
|
||||
|
||||
final calendarService = CalendarService(
|
||||
apiClient: Env.isMockApi ? null : apiClient,
|
||||
);
|
||||
final calendarService = CalendarService(apiClient: apiClient);
|
||||
sl.registerSingleton<CalendarService>(calendarService);
|
||||
|
||||
sl.registerSingleton<LocalNotificationService>(LocalNotificationService());
|
||||
@@ -72,24 +64,20 @@ Future<void> configureDependencies() async {
|
||||
final authRepository = AuthRepositoryImpl(
|
||||
api: authApi,
|
||||
tokenStorage: tokenStorage,
|
||||
onLogout: Env.isMockApi
|
||||
? null
|
||||
: () async {
|
||||
(apiClient as ApiClient).resetInterceptor();
|
||||
},
|
||||
onLogout: () async {
|
||||
(apiClient as ApiClient).resetInterceptor();
|
||||
},
|
||||
);
|
||||
sl.registerSingleton<AuthRepository>(authRepository);
|
||||
|
||||
if (!Env.isMockApi) {
|
||||
(apiClient as ApiClient).setRefreshCallback((token) async {
|
||||
try {
|
||||
await authRepository.refreshSession(token);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
(apiClient as ApiClient).setRefreshCallback((token) async {
|
||||
try {
|
||||
await authRepository.refreshSession(token);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
sl.registerSingleton<AuthBloc>(AuthBloc(authRepository));
|
||||
sl.registerSingleton<CalendarStateManager>(CalendarStateManager());
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user