This commit is contained in:
589
docs/MOBILE_INTEGRATION_EXAMPLES.md
Normal file
589
docs/MOBILE_INTEGRATION_EXAMPLES.md
Normal file
@@ -0,0 +1,589 @@
|
||||
# Примеры использования API для мобильной разработки
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
### 1. Авторизация пользователя
|
||||
```javascript
|
||||
// Регистрация
|
||||
const registerUser = async (userData) => {
|
||||
const response = await fetch('http://192.168.0.103:8000/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
username: userData.username,
|
||||
email: userData.email,
|
||||
password: userData.password,
|
||||
full_name: userData.full_name,
|
||||
phone: userData.phone
|
||||
})
|
||||
});
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
// Вход в систему
|
||||
const loginUser = async (username, password) => {
|
||||
const response = await fetch('http://192.168.0.103:8000/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
// Сохранить токен для дальнейших запросов
|
||||
localStorage.setItem('access_token', data.access_token);
|
||||
return data;
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Создание SOS оповещения
|
||||
```javascript
|
||||
const createEmergencyAlert = async (location, alertType, message) => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
const response = await fetch('http://192.168.0.103:8000/api/emergency/alerts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
alert_type: alertType, // 'general', 'medical', 'violence', etc.
|
||||
message: message,
|
||||
contact_emergency_services: true,
|
||||
notify_emergency_contacts: true
|
||||
})
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Поиск ближайших оповещений
|
||||
```javascript
|
||||
const findNearbyAlerts = async (userLocation, radiusKm = 5) => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
const response = await fetch(
|
||||
`http://192.168.0.103:8000/api/emergency/nearby?latitude=${userLocation.latitude}&longitude=${userLocation.longitude}&radius_km=${radiusKm}`,
|
||||
{
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
}
|
||||
);
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Отклик на оповещение
|
||||
```javascript
|
||||
const respondToAlert = async (alertId, responseType, message, eta) => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
const response = await fetch(`http://192.168.0.103:8000/api/emergency/alerts/${alertId}/responses`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
response_type: responseType, // 'help_on_way', 'contacted_authorities', etc.
|
||||
message: message,
|
||||
eta_minutes: eta,
|
||||
location_sharing: true
|
||||
})
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
```
|
||||
|
||||
## 📱 React Native компоненты
|
||||
|
||||
### SOS Button Component
|
||||
```jsx
|
||||
import React, { useState } from 'react';
|
||||
import { View, TouchableOpacity, Text, Alert } from 'react-native';
|
||||
import Geolocation from '@react-native-community/geolocation';
|
||||
|
||||
const SOSButton = ({ onEmergencyCreated }) => {
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
|
||||
const handleSOSPress = () => {
|
||||
Alert.alert(
|
||||
'Экстренная ситуация',
|
||||
'Вы уверены, что хотите отправить SOS сигнал?',
|
||||
[
|
||||
{ text: 'Отмена', style: 'cancel' },
|
||||
{ text: 'SOS', style: 'destructive', onPress: createEmergencyAlert }
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
const createEmergencyAlert = () => {
|
||||
setIsCreating(true);
|
||||
|
||||
Geolocation.getCurrentPosition(
|
||||
async (position) => {
|
||||
try {
|
||||
const alert = await createEmergencyAlert(
|
||||
{
|
||||
latitude: position.coords.latitude,
|
||||
longitude: position.coords.longitude
|
||||
},
|
||||
'general',
|
||||
'Экстренная помощь необходима!'
|
||||
);
|
||||
|
||||
onEmergencyCreated(alert);
|
||||
Alert.alert('Успешно', 'SOS сигнал отправлен');
|
||||
} catch (error) {
|
||||
Alert.alert('Ошибка', 'Не удалось отправить SOS сигнал');
|
||||
}
|
||||
setIsCreating(false);
|
||||
},
|
||||
(error) => {
|
||||
Alert.alert('Ошибка', 'Не удалось получить местоположение');
|
||||
setIsCreating(false);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ alignItems: 'center', margin: 20 }}>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
backgroundColor: isCreating ? '#ccc' : '#ff4444',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
elevation: 5
|
||||
}}
|
||||
onPress={handleSOSPress}
|
||||
disabled={isCreating}
|
||||
>
|
||||
<Text style={{ color: 'white', fontSize: 18, fontWeight: 'bold' }}>
|
||||
{isCreating ? 'Отправка...' : 'SOS'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Nearby Alerts List
|
||||
```jsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, FlatList, Text, TouchableOpacity } from 'react-native';
|
||||
|
||||
const NearbyAlertsList = ({ userLocation }) => {
|
||||
const [alerts, setAlerts] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (userLocation) {
|
||||
loadNearbyAlerts();
|
||||
}
|
||||
}, [userLocation]);
|
||||
|
||||
const loadNearbyAlerts = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const nearbyAlerts = await findNearbyAlerts(userLocation);
|
||||
setAlerts(nearbyAlerts);
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки оповещений:', error);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleRespondToAlert = async (alertId) => {
|
||||
try {
|
||||
await respondToAlert(alertId, 'help_on_way', 'Еду на помощь!', 10);
|
||||
Alert.alert('Успешно', 'Ваш отклик отправлен');
|
||||
loadNearbyAlerts(); // Обновить список
|
||||
} catch (error) {
|
||||
Alert.alert('Ошибка', 'Не удалось отправить отклик');
|
||||
}
|
||||
};
|
||||
|
||||
const renderAlert = ({ item }) => (
|
||||
<View style={{
|
||||
backgroundColor: 'white',
|
||||
margin: 10,
|
||||
padding: 15,
|
||||
borderRadius: 10,
|
||||
elevation: 2
|
||||
}}>
|
||||
<Text style={{ fontWeight: 'bold', fontSize: 16 }}>
|
||||
{item.alert_type.toUpperCase()}
|
||||
</Text>
|
||||
<Text style={{ color: '#666', marginTop: 5 }}>
|
||||
📍 {item.address || 'Адрес не указан'}
|
||||
</Text>
|
||||
<Text style={{ color: '#666' }}>
|
||||
📏 {item.distance_km.toFixed(1)} км от вас
|
||||
</Text>
|
||||
<Text style={{ color: '#666' }}>
|
||||
👥 Откликов: {item.responded_users_count}
|
||||
</Text>
|
||||
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
backgroundColor: '#007AFF',
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
marginTop: 10,
|
||||
alignItems: 'center'
|
||||
}}
|
||||
onPress={() => handleRespondToAlert(item.id)}
|
||||
>
|
||||
<Text style={{ color: 'white', fontWeight: 'bold' }}>
|
||||
Помочь
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ fontSize: 18, fontWeight: 'bold', margin: 15 }}>
|
||||
Ближайшие оповещения
|
||||
</Text>
|
||||
|
||||
{loading ? (
|
||||
<Text style={{ textAlign: 'center', margin: 20 }}>
|
||||
Загрузка...
|
||||
</Text>
|
||||
) : (
|
||||
<FlatList
|
||||
data={alerts}
|
||||
renderItem={renderAlert}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
refreshing={loading}
|
||||
onRefresh={loadNearbyAlerts}
|
||||
ListEmptyComponent={
|
||||
<Text style={{ textAlign: 'center', margin: 20, color: '#666' }}>
|
||||
Поблизости нет активных оповещений
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 🔄 Обновление местоположения в реальном времени
|
||||
|
||||
```javascript
|
||||
class LocationService {
|
||||
constructor() {
|
||||
this.watchId = null;
|
||||
this.lastUpdate = null;
|
||||
}
|
||||
|
||||
startLocationTracking() {
|
||||
this.watchId = Geolocation.watchPosition(
|
||||
(position) => {
|
||||
this.updateLocation(position.coords);
|
||||
},
|
||||
(error) => {
|
||||
console.error('Location error:', error);
|
||||
},
|
||||
{
|
||||
enableHighAccuracy: true,
|
||||
distanceFilter: 10, // Обновлять при изменении на 10 метров
|
||||
interval: 30000, // Обновлять каждые 30 секунд
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async updateLocation(coords) {
|
||||
const now = Date.now();
|
||||
|
||||
// Не отправляем слишком часто
|
||||
if (this.lastUpdate && (now - this.lastUpdate) < 30000) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
await fetch('http://192.168.0.103:8000/api/location/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
latitude: coords.latitude,
|
||||
longitude: coords.longitude,
|
||||
accuracy: coords.accuracy,
|
||||
speed: coords.speed,
|
||||
heading: coords.heading,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
});
|
||||
|
||||
this.lastUpdate = now;
|
||||
} catch (error) {
|
||||
console.error('Failed to update location:', error);
|
||||
}
|
||||
}
|
||||
|
||||
stopLocationTracking() {
|
||||
if (this.watchId) {
|
||||
Geolocation.clearWatch(this.watchId);
|
||||
this.watchId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔔 Push уведомления
|
||||
|
||||
### Настройка Firebase (Android) / APNS (iOS)
|
||||
```javascript
|
||||
import messaging from '@react-native-firebase/messaging';
|
||||
|
||||
class PushNotificationService {
|
||||
async requestPermission() {
|
||||
const authStatus = await messaging().requestPermission();
|
||||
const enabled =
|
||||
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
|
||||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
|
||||
|
||||
if (enabled) {
|
||||
console.log('Push permissions granted');
|
||||
await this.getToken();
|
||||
}
|
||||
}
|
||||
|
||||
async getToken() {
|
||||
try {
|
||||
const token = await messaging().getToken();
|
||||
await this.registerToken(token);
|
||||
} catch (error) {
|
||||
console.error('Failed to get push token:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async registerToken(token) {
|
||||
const apiToken = localStorage.getItem('access_token');
|
||||
|
||||
await fetch('http://192.168.0.103:8000/api/notifications/register-token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiToken}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: token,
|
||||
platform: Platform.OS, // 'ios' or 'android'
|
||||
app_version: '1.0.0'
|
||||
});
|
||||
}
|
||||
|
||||
setupForegroundHandler() {
|
||||
messaging().onMessage(async remoteMessage => {
|
||||
// Показать уведомление когда приложение активно
|
||||
Alert.alert(
|
||||
remoteMessage.notification.title,
|
||||
remoteMessage.notification.body
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
setupBackgroundHandler() {
|
||||
messaging().setBackgroundMessageHandler(async remoteMessage => {
|
||||
console.log('Background message:', remoteMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔐 Безопасность и шифрование
|
||||
|
||||
### Хранение токенов
|
||||
```javascript
|
||||
import * as Keychain from 'react-native-keychain';
|
||||
|
||||
class SecureStorage {
|
||||
static async storeToken(token) {
|
||||
try {
|
||||
await Keychain.setInternetCredentials(
|
||||
'women_safety_app',
|
||||
'access_token',
|
||||
token
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to store token:', error);
|
||||
}
|
||||
}
|
||||
|
||||
static async getToken() {
|
||||
try {
|
||||
const credentials = await Keychain.getInternetCredentials('women_safety_app');
|
||||
if (credentials) {
|
||||
return credentials.password;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get token:', error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static async removeToken() {
|
||||
try {
|
||||
await Keychain.resetInternetCredentials('women_safety_app');
|
||||
} catch (error) {
|
||||
console.error('Failed to remove token:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Шифрование местоположения
|
||||
```javascript
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
class LocationEncryption {
|
||||
static encryptLocation(lat, lng, secretKey) {
|
||||
const locationData = JSON.stringify({ lat, lng, timestamp: Date.now() });
|
||||
return CryptoJS.AES.encrypt(locationData, secretKey).toString();
|
||||
}
|
||||
|
||||
static decryptLocation(encryptedData, secretKey) {
|
||||
try {
|
||||
const bytes = CryptoJS.AES.decrypt(encryptedData, secretKey);
|
||||
const decryptedData = bytes.toString(CryptoJS.enc.Utf8);
|
||||
return JSON.parse(decryptedData);
|
||||
} catch (error) {
|
||||
console.error('Failed to decrypt location:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Аналитика и мониторинг
|
||||
|
||||
### Отслеживание использования
|
||||
```javascript
|
||||
class AnalyticsService {
|
||||
static trackEmergencyAlert(alertType, responseTime) {
|
||||
// Интеграция с аналитикой (Firebase Analytics, etc.)
|
||||
analytics().logEvent('emergency_alert_created', {
|
||||
alert_type: alertType,
|
||||
response_time_seconds: responseTime,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
static trackUserResponse(alertId, responseType) {
|
||||
analytics().logEvent('emergency_response', {
|
||||
alert_id: alertId,
|
||||
response_type: responseType,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
static trackLocationUpdate(accuracy) {
|
||||
analytics().logEvent('location_update', {
|
||||
accuracy_meters: accuracy,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 Тестирование API
|
||||
|
||||
### Unit тесты для API клиента
|
||||
```javascript
|
||||
import { APIClient } from './APIClient';
|
||||
|
||||
describe('APIClient', () => {
|
||||
let client;
|
||||
|
||||
beforeEach(() => {
|
||||
client = new APIClient('http://192.168.0.103:8000');
|
||||
});
|
||||
|
||||
test('should login successfully', async () => {
|
||||
const result = await client.login('testuser', 'testpass');
|
||||
expect(result.access_token).toBeDefined();
|
||||
expect(result.token_type).toBe('bearer');
|
||||
});
|
||||
|
||||
test('should create emergency alert', async () => {
|
||||
// Сначала авторизоваться
|
||||
await client.login('testuser', 'testpass');
|
||||
|
||||
const alert = await client.createAlert({
|
||||
latitude: 55.7558,
|
||||
longitude: 37.6176,
|
||||
alert_type: 'general',
|
||||
message: 'Test emergency'
|
||||
});
|
||||
|
||||
expect(alert.id).toBeDefined();
|
||||
expect(alert.alert_type).toBe('general');
|
||||
expect(alert.status).toBe('active');
|
||||
});
|
||||
|
||||
test('should get nearby alerts', async () => {
|
||||
await client.login('testuser', 'testpass');
|
||||
|
||||
const alerts = await client.getNearbyAlerts(55.7558, 37.6176, 5);
|
||||
expect(Array.isArray(alerts)).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 📈 Оптимизация производительности
|
||||
|
||||
### Кэширование данных
|
||||
```javascript
|
||||
class CacheService {
|
||||
static cache = new Map();
|
||||
static CACHE_DURATION = 5 * 60 * 1000; // 5 минут
|
||||
|
||||
static set(key, data) {
|
||||
this.cache.set(key, {
|
||||
data,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
static get(key) {
|
||||
const cached = this.cache.get(key);
|
||||
if (!cached) return null;
|
||||
|
||||
if (Date.now() - cached.timestamp > this.CACHE_DURATION) {
|
||||
this.cache.delete(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
static async getNearbyAlertsWithCache(lat, lng, radius) {
|
||||
const cacheKey = `nearby_${lat}_${lng}_${radius}`;
|
||||
let alerts = this.get(cacheKey);
|
||||
|
||||
if (!alerts) {
|
||||
alerts = await findNearbyAlerts({ latitude: lat, longitude: lng }, radius);
|
||||
this.set(cacheKey, alerts);
|
||||
}
|
||||
|
||||
return alerts;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Эти примеры покрывают все основные сценарии использования API в реальном мобильном приложении, включая безопасность, производительность и тестирование.
|
||||
Reference in New Issue
Block a user