feat: 文件存储安全加固 + 认证增强 + 媒体URL保护 + provider 重构

## 后端安全加固
- 新增 UserUploadPathResolver: 用户上传文件路径安全解析, 防目录穿越
- LocalReportFileStorage: 文件存储路径安全加固
- local_account_file_cleanup: 账号删除时文件清理逻辑增强
- AuthService: 认证逻辑增强
- file_endpoints / report_endpoints: 文件访问接口安全加固
- ai_chat_endpoints / doctor_endpoints: 接口安全调整
- Program.cs: 服务注册调整

## 前端认证与媒体
- 新增 authenticated_network_image.dart: 带认证的图片加载组件
- auth_provider: 认证状态管理大幅增强(+173)
- api_client: 网络客户端增强(+124)
- chat_provider: 聊天 provider 重构(+76)
- omron_device_provider: 蓝牙设备 provider 增强(+53)
- sse_handler: SSE 处理增强(+35)
- consultation_provider / data_providers / conversation_history_provider: 调整

## 页面调整
- remaining_pages: 健康档案/饮食记录等页面增强(+115)
- home_page / chat_messages_view: 主页微调
- doctor 端多页微调(consultations/dashboard/followups/patient_detail/profile/report_detail/reports)
- report_pages / settings_pages / notification_prefs_page: 微调
- device_scan_page / diet_capture_page / admin_home_page: 微调

## 测试
- 新增 file_path_security_tests: 文件路径安全测试
- 新增 protected_media_url_test: 媒体URL保护测试
- 新增 user_session_identity_test: 用户会话身份测试
- account_deletion_tests / application_service_tests / auth_tests: 更新
This commit is contained in:
MingNian
2026-07-20 10:19:01 +08:00
parent 0d4fd88ce7
commit 9cea41705e
48 changed files with 1181 additions and 212 deletions

View File

@@ -14,7 +14,7 @@ class SseHandler {
String? pdfUrl,
required String token,
}) {
final params = <String, String>{'message': message, 'token': token};
final params = <String, String>{'message': message};
if (conversationId != null) {
params['conversationId'] = conversationId;
}
@@ -29,14 +29,26 @@ class SseHandler {
.join('&');
final url = '$baseUrl/api/ai/$agentType/chat?$query';
final controller = StreamController<Map<String, dynamic>>();
_connect(controller, url);
final cancelToken = CancelToken();
late final StreamController<Map<String, dynamic>> controller;
controller = StreamController<Map<String, dynamic>>(
onListen: () {
_connect(controller, url, token, cancelToken);
},
onCancel: () {
if (!cancelToken.isCancelled) {
cancelToken.cancel('用户停止生成');
}
},
);
return controller.stream;
}
static Future<void> _connect(
StreamController<Map<String, dynamic>> controller,
String url,
String token,
CancelToken cancelToken,
) async {
try {
final dio = Dio(
@@ -48,7 +60,11 @@ class SseHandler {
final response = await dio.get(
url,
options: Options(responseType: ResponseType.stream),
cancelToken: cancelToken,
options: Options(
responseType: ResponseType.stream,
headers: {'Authorization': 'Bearer $token'},
),
);
final stream = response.data.stream as Stream<List<int>>;
@@ -81,10 +97,19 @@ class SseHandler {
}
}
controller.close();
} on DioException catch (e) {
if (CancelToken.isCancel(e)) {
if (!controller.isClosed) await controller.close();
return;
}
if (!controller.isClosed) {
controller.add({'action': 'error', 'message': e.toString()});
await controller.close();
}
} catch (e) {
if (!controller.isClosed) {
controller.add({'action': 'error', 'message': e.toString()});
controller.close();
await controller.close();
}
}
}