Files
AI-Health/health_app/lib/providers/conversation_history_provider.dart
MingNian 9cea41705e 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: 更新
2026-07-20 10:19:01 +08:00

87 lines
2.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/auth_provider.dart';
import '../providers/chat_provider.dart';
/// 对话历史列表项
class ConversationListItem {
final String id;
final String? title;
final String? summary;
final int messageCount;
final DateTime updatedAt;
const ConversationListItem({
required this.id,
this.title,
this.summary,
required this.messageCount,
required this.updatedAt,
});
factory ConversationListItem.fromJson(Map<String, dynamic> json) =>
ConversationListItem(
id: json['id']?.toString() ?? '',
title: json['title']?.toString(),
summary: json['summary']?.toString(),
messageCount: (json['messageCount'] as num?)?.toInt() ?? 0,
updatedAt:
DateTime.tryParse(json['updatedAt']?.toString() ?? '')?.toLocal() ??
DateTime.now(),
);
}
/// 对话历史 Notifier缓存列表提供刷新/删除/清空。
class ConversationHistoryNotifier
extends AsyncNotifier<List<ConversationListItem>> {
@override
Future<List<ConversationListItem>> build() {
final session = ref.watch(userSessionIdentityProvider);
if (session == null) return Future.value(const []);
return _fetch();
}
Future<List<ConversationListItem>> _fetch() async {
final api = ref.read(apiClientProvider);
final res = await api.get('/api/ai/conversations');
final raw = (res.data['data'] as List?) ?? const [];
return raw
.whereType<Map>()
.map((m) => ConversationListItem.fromJson(Map<String, dynamic>.from(m)))
.toList();
}
Future<void> refresh() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(_fetch);
}
/// 删除单条;后端确认成功后再更新 UI。
Future<void> deleteOne(String id) async {
final previous = state.asData?.value ?? const <ConversationListItem>[];
final api = ref.read(apiClientProvider);
await api.delete('/api/ai/conversations/$id');
state = AsyncValue.data(previous.where((e) => e.id != id).toList());
// 同步清掉当前 chat state如果删的就是当前会话
final chat = ref.read(chatProvider);
if (chat.conversationId == id) {
await ref.read(chatProvider.notifier).resetSession();
}
}
/// 一键清空当前用户的全部对话。
Future<int> clearAll() async {
final api = ref.read(apiClientProvider);
final res = await api.delete('/api/ai/conversations');
state = const AsyncValue.data([]);
await ref.read(chatProvider.notifier).resetSession();
final deleted = res.data['data']?['deleted'];
return deleted is num ? deleted.toInt() : 0;
}
}
final conversationHistoryProvider =
AsyncNotifierProvider<
ConversationHistoryNotifier,
List<ConversationListItem>
>(ConversationHistoryNotifier.new);