feat: 后端架构重构 — Endpoint→Service→Repository分层 + AI确认机制 + 异步任务持久化
- 核心业务拆分为 Endpoint → Application Service → Repository 三层 - AI写入操作必须用户确认后才写库(确认卡片机制) - 报告/饮食/用药分析改为持久化任务队列(原子领取/重试/重启恢复) - 运动计划修复: 连续真实日期替代周模板 - 用药提醒去重 + 通知Outbox预留 - 认证收拢到AuthService, 管理员收拢到AdminService - AI会话加用户归属校验防串号 - 提示词调整为患者视角 - 开发假数据已关闭 - 21/21测试通过, 0警告0错误
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'local_database.dart';
|
||||
@@ -19,7 +20,7 @@ class ApiClient {
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
)) {
|
||||
_dio.interceptors.add(_AuthInterceptor(this));
|
||||
_dio.interceptors.add(LogInterceptor(requestBody: true, responseBody: false));
|
||||
_dio.interceptors.add(LogInterceptor(requestBody: false, responseBody: false));
|
||||
}
|
||||
|
||||
Dio get dio => _dio;
|
||||
@@ -33,7 +34,8 @@ class ApiClient {
|
||||
}
|
||||
|
||||
Future<void> clearTokens() async {
|
||||
await _db.deleteAll();
|
||||
await _db.delete('access_token');
|
||||
await _db.delete('refresh_token');
|
||||
}
|
||||
|
||||
/// 带 token 的 GET 请求
|
||||
@@ -64,7 +66,19 @@ class ApiClient {
|
||||
final res = await _dio.post(path, data: formData);
|
||||
final data = res.data;
|
||||
if (data is Map) {
|
||||
return data['url']?.toString() ?? data['data']?['url']?.toString();
|
||||
final topUrl = data['url']?.toString();
|
||||
if (topUrl != null && topUrl.isNotEmpty) return topUrl;
|
||||
|
||||
final payload = data['data'];
|
||||
if (payload is Map) {
|
||||
final url = payload['url']?.toString();
|
||||
return url != null && url.isNotEmpty ? url : null;
|
||||
}
|
||||
if (payload is List && payload.isNotEmpty && payload.first is Map) {
|
||||
final first = payload.first as Map;
|
||||
final url = first['url']?.toString();
|
||||
return url != null && url.isNotEmpty ? url : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -104,7 +118,7 @@ class _AuthInterceptor extends Interceptor {
|
||||
final retryResponse = await Dio(BaseOptions(baseUrl: baseUrl)).fetch(opts);
|
||||
return handler.resolve(retryResponse);
|
||||
}
|
||||
} catch (e) { print('[ApiClient] token刷新失败: $e'); }
|
||||
} catch (e) { log('[ApiClient] token刷新失败: $e'); }
|
||||
}
|
||||
await _client.clearTokens();
|
||||
}
|
||||
|
||||
@@ -58,6 +58,11 @@ Widget buildPage(RouteInfo route, WidgetRef ref) {
|
||||
return const ReportListPage();
|
||||
case 'aiAnalysis':
|
||||
return AiAnalysisPage(id: params['id']!);
|
||||
case 'reportOriginal':
|
||||
return ReportOriginalPage(
|
||||
url: params['url'] ?? '',
|
||||
title: params['title'] ?? '原始报告',
|
||||
);
|
||||
case 'exercisePlan':
|
||||
return const ExercisePlanPage();
|
||||
case 'exerciseCreate':
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
/// SQLite 本地数据库——存储 token 等关键信息
|
||||
/// SQLite 本地键值缓存。
|
||||
///
|
||||
/// 约定:后端数据库是健康业务数据的唯一事实源。这里仅保存会话、偏好、
|
||||
/// 草稿和临时缓存,不保存健康记录、报告、用药、饮食、AI 对话等核心业务事实。
|
||||
class LocalDatabase {
|
||||
static LocalDatabase? _instance;
|
||||
Database? _db;
|
||||
|
||||
Reference in New Issue
Block a user