Руководство по интеграции API сервера CulicidaeLab¶
Обзор¶
Мобильное приложение CulicidaeLab интегрируется с экосистемой сервера CulicidaeLab для предоставления расширенных возможностей классификации комаров и вклада в глобальные исследования наблюдения за комарами. Это руководство предоставляет комплексную документацию для разработчиков и исследователей, работающих с интеграцией серверного API.
Базовая конфигурация¶
Конечные точки сервера¶
Сервер CulicidaeLab предоставляет следующие основные конечные точки:
// Базовый URL сервера
const String baseUrl = "https://culicidealab.ru";
// Конечные точки API
const String predictionEndpoint = "/api/predict";
const String observationsEndpoint = "/api/observations";
const String mapEndpoint = "/map";
Настройка HTTP клиента¶
Приложение использует стандартный пакет Dart http для связи с сервером:
import 'package:http/http.dart' as http;
// Регистрация HTTP клиента во внедрении зависимостей
locator.registerLazySingleton(() => http.Client());
// Использование в репозиториях
final http.Client _httpClient = locator<http.Client>();
Аутентификация¶
Текущая реализация¶
Текущая реализация API не требует аутентификации для базовых операций: - Запросы классификации изображений - Отправка наблюдений - Доступ к данным карты
Будущая поддержка аутентификации¶
Для расширенных функций и исследовательского доступа API может реализовать: - Аутентификацию по API ключу для ограничения скорости - OAuth2 для доступа к пользовательским данным - JWT токены для управления сессиями
// Пример будущего заголовка аутентификации
final headers = {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer $apiToken', // Будущая реализация
};
```#
# API классификации изображений
### Конечная точка: `/api/predict`
Предоставляет серверную классификацию видов комаров с использованием продвинутых моделей машинного обучения.
#### Формат запроса
```http
POST https://culicidealab.ru/api/predict
Content-Type: multipart/form-data
--boundary
Content-Disposition: form-data; name="file"; filename="mosquito.jpg"
Content-Type: image/jpeg
[Бинарные данные изображения]
--boundary--
Пример реализации¶
Future<WebPredictionResult> getWebPrediction(File imageFile) async {
final url = Uri.parse("https://culicidealab.ru/api/predict");
var request = http.MultipartRequest('POST', url);
// Определить MIME тип из расширения файла
String mimeType = 'image/jpeg'; // По умолчанию
if (imageFile.path.toLowerCase().endsWith('.png')) {
mimeType = 'image/png';
} else if (imageFile.path.toLowerCase().endsWith('.webp')) {
mimeType = 'image/webp';
}
// Создать multipart файл
final multipartFile = await http.MultipartFile.fromPath(
'file',
imageFile.path,
contentType: MediaType.parse(mimeType),
);
request.files.add(multipartFile);
// Отправить запрос
final streamedResponse = await _httpClient.send(request);
final response = await http.Response.fromStream(streamedResponse);
if (response.statusCode == 200) {
return WebPredictionResult.fromJson(json.decode(response.body));
} else {
throw Exception('Предсказание не удалось: ${response.statusCode}');
}
}
Формат ответа¶
{
"id": "pred_123456789",
"scientific_name": "Aedes aegypti",
"common_name": "Комар желтой лихорадки",
"confidence": 0.87,
"probabilities": {
"Aedes aegypti": 0.87,
"Aedes albopictus": 0.09,
"Culex pipiens": 0.03,
"Other": 0.01
},
"processing_time_ms": 245,
"model_version": "v2.1.0",
"timestamp": "2024-01-15T10:30:00Z"
}
Модель ответа¶
class WebPredictionResult {
final String id;
final String scientificName;
final String commonName;
final double confidence;
final Map<String, double> probabilities;
final int processingTimeMs;
final String modelVersion;
final DateTime timestamp;
WebPredictionResult({
required this.id,
required this.scientificName,
required this.commonName,
required this.confidence,
required this.probabilities,
required this.processingTimeMs,
required this.modelVersion,
required this.timestamp,
});
factory WebPredictionResult.fromJson(Map<String, dynamic> json) {
return WebPredictionResult(
id: json['id'] as String,
scientificName: json['scientific_name'] as String,
commonName: json['common_name'] as String,
confidence: (json['confidence'] as num).toDouble(),
probabilities: Map<String, double>.from(
json['probabilities'].map((k, v) => MapEntry(k, (v as num).toDouble()))
),
processingTimeMs: json['processing_time_ms'] as int,
modelVersion: json['model_version'] as String,
timestamp: DateTime.parse(json['timestamp'] as String),
);
}
}
```#
# API отправки наблюдений
### Конечная точка: `/api/observations`
Позволяет отправку данных наблюдений комаров для исследовательских целей и наблюдения.
#### Формат запроса
```http
POST https://culicidealab.ru/api/observations
Content-Type: application/json; charset=UTF-8
{
"species_scientific_name": "Aedes aegypti",
"species_common_name": "Комар желтой лихорадки",
"confidence": 0.87,
"latitude": 40.7128,
"longitude": -74.0060,
"timestamp": "2024-01-15T10:30:00Z",
"notes": "Найден рядом со стоячей водой",
"image_path": "/path/to/image.jpg",
"prediction_id": "pred_123456789",
"model_version": "v2.1.0",
"processing_time_ms": 245,
"probabilities": {
"Aedes aegypti": 0.87,
"Aedes albopictus": 0.09,
"Culex pipiens": 0.03
}
}
Пример реализации¶
Future<Observation> submitObservation({
required Map<String, dynamic> finalPayload,
}) async {
final url = Uri.parse("https://culicidealab.ru/api/observations");
final response = await _httpClient.post(
url,
headers: {'Content-Type': 'application/json; charset=UTF-8'},
body: json.encode(finalPayload),
);
if (response.statusCode == 201) {
return Observation.fromJson(json.decode(response.body));
} else {
throw Exception('Не удалось отправить наблюдение: ${response.statusCode} - ${response.body}');
}
}
Конструирование полезной нагрузки¶
Приложение поддерживает два типа отправки наблюдений:
Только локальные предсказания:
final localOnlyPayload = {
"species_scientific_name": localResult.species.name,
"species_common_name": localResult.species.commonName,
"confidence": localResult.confidence,
"latitude": latitude,
"longitude": longitude,
"timestamp": DateTime.now().toIso8601String(),
"notes": userNotes,
"image_path": imagePath,
"prediction_source": "local_model",
"model_version": "local_v1.0",
"processing_time_ms": localResult.processingTimeMs,
};
Веб-предсказания с полным распределением вероятностей:
final webPredictionPayload = {
"species_scientific_name": webPrediction.scientificName,
"species_common_name": webPrediction.commonName,
"confidence": webPrediction.confidence,
"latitude": latitude,
"longitude": longitude,
"timestamp": DateTime.now().toIso8601String(),
"notes": userNotes,
"image_path": imagePath,
"prediction_id": webPrediction.id,
"model_version": webPrediction.modelVersion,
"processing_time_ms": webPrediction.processingTimeMs,
"probabilities": webPrediction.probabilities,
"prediction_source": "server_model",
};
```#
# Обработка ошибок
### HTTP коды состояния
API использует стандартные HTTP коды состояния:
- `200 OK` - Успешный запрос предсказания
- `201 Created` - Успешная отправка наблюдения
- `400 Bad Request` - Неверный формат запроса или отсутствующие параметры
- `413 Payload Too Large` - Файл изображения слишком большой
- `415 Unsupported Media Type` - Неверный формат изображения
- `429 Too Many Requests` - Превышен лимит скорости
- `500 Internal Server Error` - Ошибка обработки сервера
- `503 Service Unavailable` - Обслуживание сервера или перегрузка
### Формат ответа об ошибке
```json
{
"error": {
"code": "INVALID_IMAGE_FORMAT",
"message": "Неподдерживаемый формат изображения. Используйте JPEG, PNG или WebP.",
"details": {
"supported_formats": ["image/jpeg", "image/png", "image/webp"],
"max_file_size": "10MB"
}
},
"timestamp": "2024-01-15T10:30:00Z",
"request_id": "req_123456789"
}
Реализация обработки исключений¶
Future<WebPredictionResult> getWebPrediction(File imageFile) async {
try {
// Реализация вызова API
final response = await _httpClient.send(request);
if (response.statusCode == 200) {
return WebPredictionResult.fromJson(json.decode(response.body));
} else {
throw ApiException(
statusCode: response.statusCode,
message: 'Предсказание не удалось',
body: response.body,
);
}
} on SocketException {
throw NetworkException('Нет интернет-соединения');
} on TimeoutException {
throw NetworkException('Тайм-аут запроса');
} on FormatException {
throw ApiException(
statusCode: 0,
message: 'Неверный формат ответа',
body: '',
);
}
}
class ApiException implements Exception {
final int statusCode;
final String message;
final String body;
ApiException({
required this.statusCode,
required this.message,
required this.body,
});
@override
String toString() => 'ApiException: $statusCode - $message';
}
class NetworkException implements Exception {
final String message;
NetworkException(this.message);
@override
String toString() => 'NetworkException: $message';
}
Ограничения скорости и производительность¶
Текущие ограничения¶
- Явные ограничения скорости не реализованы
- Размер изображения должен быть оптимизирован перед загрузкой
- Тайм-ауты сети обрабатываются настройками HTTP клиента по умолчанию
Лучшие практики¶
// Оптимизация изображения перед загрузкой
Future<File> optimizeImageForUpload(File originalImage) async {
final bytes = await originalImage.readAsBytes();
final image = img.decodeImage(bytes);
if (image == null) throw Exception('Неверный формат изображения');
// Изменить размер если слишком большое (макс 1024x1024)
final resized = image.width > 1024 || image.height > 1024
? img.copyResize(image, width: 1024, height: 1024)
: image;
// Сжать в JPEG с качеством 85%
final compressed = img.encodeJpg(resized, quality: 85);
// Сохранить оптимизированное изображение
final tempDir = await getTemporaryDirectory();
final optimizedFile = File('${tempDir.path}/optimized_${DateTime.now().millisecondsSinceEpoch}.jpg');
await optimizedFile.writeAsBytes(compressed);
return optimizedFile;
}
// Конфигурация тайм-аута запроса
final client = http.Client();
final request = http.MultipartRequest('POST', url);
// Установить пользовательский тайм-аут
final response = await client.send(request).timeout(
const Duration(seconds: 30),
onTimeout: () => throw TimeoutException('Тайм-аут запроса'),
);
```#
# Соображения безопасности
### Конфиденциальность данных
- Изображения обрабатываются на сервере, но не сохраняются постоянно
- Данные о местоположении отправляются только с явного согласия пользователя
- Персональные идентификационные данные не собираются
### Сетевая безопасность
- Все API коммуникации используют HTTPS
- Нет чувствительных токенов аутентификации в текущей реализации
- Данные запроса/ответа не кэшируются локально
### Валидация входных данных
```dart
// Валидировать файл изображения перед загрузкой
bool isValidImageFile(File imageFile) {
final extension = path.extension(imageFile.path).toLowerCase();
final validExtensions = ['.jpg', '.jpeg', '.png', '.webp'];
if (!validExtensions.contains(extension)) {
return false;
}
// Проверить размер файла (макс 10МБ)
final fileSizeBytes = imageFile.lengthSync();
const maxSizeBytes = 10 * 1024 * 1024; // 10МБ
return fileSizeBytes <= maxSizeBytes;
}
Будущие улучшения API¶
Запланированные функции¶
- Пакетная обработка: Отправка нескольких наблюдений в одном запросе
- Обновления в реальном времени: WebSocket соединения для живых обновлений карты
- Продвинутая фильтрация: Запрос наблюдений по видам, местоположению, диапазону дат
- Пользовательские аккаунты: Личная история наблюдений и статистика
- Экспорт данных: Загрузка данных наблюдений в различных форматах
Версионирование API¶
Будущие версии API будут поддерживаться через версионирование URL:
const String apiV2BaseUrl = "https://culicidealab.ru/api/v2";
const String apiV3BaseUrl = "https://culicidealab.ru/api/v3";
Устранение неполадок¶
Распространенные проблемы¶
Сетевое подключение
// Проверить сетевое подключение перед вызовами API
Future<bool> hasNetworkConnection() async {
try {
final result = await InternetAddress.lookup('culicidealab.ru');
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
} on SocketException catch (_) {
return false;
}
}
Большие файлы изображений - Оптимизируйте изображения перед загрузкой используя сжатие изображений - Рассмотрите реализацию прогрессивной загрузки для больших файлов
Тайм-ауты сервера - Реализуйте логику повторных попыток с экспоненциальной задержкой - Показывайте соответствующую обратную связь пользователю во время длительных операций
Поддержка и ресурсы¶
- Документация API: https://culicidealab.ru/docs/api
- Статус сервера: https://culicidealab.ru/health
- Сообщение о проблемах: https://github.com/iloncka-ds/culicidaelab-mobile/issues
Для дополнительной поддержки или вопросов об интеграции API, пожалуйста, свяжитесь с командой разработчиков или создайте задачу в репозитории проекта.