init commit
This commit is contained in:
631
.history/REWRITE_INSTRUCTION_20251009090211.md
Normal file
631
.history/REWRITE_INSTRUCTION_20251009090211.md
Normal file
@@ -0,0 +1,631 @@
|
||||
# 🛠️ ИНСТРУКЦИЯ ПО ПЕРЕПИСЫВАНИЮ СИСТЕМЫ VIDEOREADER
|
||||
|
||||
**Версия:** 1.0
|
||||
**Дата:** 9 октября 2025 г.
|
||||
**Цель:** Создание собственной безопасной системы видеонаблюдения
|
||||
|
||||
---
|
||||
|
||||
## 📋 ПЛАН МИГРАЦИИ
|
||||
|
||||
### Этап 1: Анализ и планирование (1-2 недели)
|
||||
### Этап 2: Инфраструктура (2-3 недели)
|
||||
### Етап 3: Backend разработка (3-4 недели)
|
||||
### Этап 4: Клиентские приложения (4-6 недель)
|
||||
### Этап 5: Тестирование и деплой (2 недели)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ СОВРЕМЕННАЯ АРХИТЕКТУРА
|
||||
|
||||
### Рекомендуемый стек технологий:
|
||||
|
||||
#### 🌐 Backend (Сигналинг сервер)
|
||||
```
|
||||
├── Node.js + TypeScript
|
||||
├── Socket.IO / WebSocket
|
||||
├── Redis (для сессий)
|
||||
├── PostgreSQL (метаданные)
|
||||
├── Docker + Docker Compose
|
||||
└── Nginx (Reverse Proxy)
|
||||
```
|
||||
|
||||
#### 📱 Android приложение
|
||||
```
|
||||
├── Kotlin / Java
|
||||
├── WebRTC Android API
|
||||
├── Camera2 API
|
||||
├── Retrofit (HTTP клиент)
|
||||
├── Room (локальная БД)
|
||||
└── Dagger/Hilt (DI)
|
||||
```
|
||||
|
||||
#### 💻 Desktop приложение
|
||||
```
|
||||
Вариант 1: Electron + React/Vue
|
||||
├── TypeScript
|
||||
├── WebRTC Web API
|
||||
├── FFmpeg.js
|
||||
└── Material-UI / Ant Design
|
||||
|
||||
Вариант 2: .NET MAUI
|
||||
├── C# .NET 8
|
||||
├── WebRTC.NET
|
||||
├── FFMpegCore
|
||||
└── Avalonia UI
|
||||
|
||||
Вариант 3: Flutter Desktop
|
||||
├── Dart
|
||||
├── WebRTC Flutter plugin
|
||||
├── FFmpeg Flutter
|
||||
└── Material Design
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 ЭТАП 1: НАСТРОЙКА ИНФРАСТРУКТУРЫ
|
||||
|
||||
### 1.1 Создание сигналинг сервера
|
||||
|
||||
```bash
|
||||
# Создание проекта
|
||||
mkdir videoreader-signaling
|
||||
cd videoreader-signaling
|
||||
npm init -y
|
||||
|
||||
# Установка зависимостей
|
||||
npm install express socket.io redis ioredis
|
||||
npm install @types/node typescript ts-node nodemon --save-dev
|
||||
```
|
||||
|
||||
### 1.2 Базовая структура сервера
|
||||
|
||||
```typescript
|
||||
// src/server.ts
|
||||
import express from 'express';
|
||||
import { createServer } from 'http';
|
||||
import { Server } from 'socket.io';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const app = express();
|
||||
const server = createServer(app);
|
||||
const io = new Server(server, {
|
||||
cors: { origin: "*" }
|
||||
});
|
||||
|
||||
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
|
||||
|
||||
interface Device {
|
||||
id: string;
|
||||
type: 'sender' | 'receiver';
|
||||
channel: string;
|
||||
socketId: string;
|
||||
}
|
||||
|
||||
// Управление каналами и устройствами
|
||||
class ChannelManager {
|
||||
async addDevice(device: Device) {
|
||||
await redis.hset(`channel:${device.channel}`, device.type, device.socketId);
|
||||
await redis.expire(`channel:${device.channel}`, 3600); // TTL 1 час
|
||||
}
|
||||
|
||||
async getPartner(channel: string, deviceType: string) {
|
||||
const partnerType = deviceType === 'sender' ? 'receiver' : 'sender';
|
||||
return await redis.hget(`channel:${channel}`, partnerType);
|
||||
}
|
||||
}
|
||||
|
||||
const channelManager = new ChannelManager();
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
socket.on('join-channel', async (data: {channel: string, type: 'sender'|'receiver'}) => {
|
||||
await channelManager.addDevice({
|
||||
id: socket.id,
|
||||
type: data.type,
|
||||
channel: data.channel,
|
||||
socketId: socket.id
|
||||
});
|
||||
|
||||
// Поиск партнера
|
||||
const partner = await channelManager.getPartner(data.channel, data.type);
|
||||
if (partner) {
|
||||
socket.emit('partner-found', { partnerId: partner });
|
||||
io.to(partner).emit('partner-found', { partnerId: socket.id });
|
||||
}
|
||||
});
|
||||
|
||||
// WebRTC signaling
|
||||
socket.on('webrtc-offer', (data) => {
|
||||
io.to(data.to).emit('webrtc-offer', { from: socket.id, offer: data.offer });
|
||||
});
|
||||
|
||||
socket.on('webrtc-answer', (data) => {
|
||||
io.to(data.to).emit('webrtc-answer', { from: socket.id, answer: data.answer });
|
||||
});
|
||||
|
||||
socket.on('webrtc-ice-candidate', (data) => {
|
||||
io.to(data.to).emit('webrtc-ice-candidate', { from: socket.id, candidate: data.candidate });
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(3000, () => console.log('Signaling server running on port 3000'));
|
||||
```
|
||||
|
||||
### 1.3 Docker конфигурация
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
signaling:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
- ./ssl:/etc/nginx/ssl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 ЭТАП 2: ANDROID ПРИЛОЖЕНИЕ (KOTLIN)
|
||||
|
||||
### 2.1 Создание проекта
|
||||
|
||||
```kotlin
|
||||
// build.gradle (Module: app)
|
||||
dependencies {
|
||||
implementation 'org.webrtc:google-webrtc:1.0.32006'
|
||||
implementation 'io.socket:socket.io-client:2.0.0'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation 'androidx.camera:camera-camera2:1.3.0'
|
||||
implementation 'androidx.camera:camera-lifecycle:1.3.0'
|
||||
implementation 'androidx.camera:camera-view:1.3.0'
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 WebRTC интеграция
|
||||
|
||||
```kotlin
|
||||
// WebRTCManager.kt
|
||||
class WebRTCManager(private val context: Context) {
|
||||
private var peerConnection: PeerConnection? = null
|
||||
private var localVideoTrack: VideoTrack? = null
|
||||
private var socket: Socket? = null
|
||||
|
||||
fun initialize() {
|
||||
// Инициализация PeerConnectionFactory
|
||||
PeerConnectionFactory.initializeAndroidGlobals(
|
||||
context,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
)
|
||||
|
||||
val factory = PeerConnectionFactory.builder()
|
||||
.createPeerConnectionFactory()
|
||||
|
||||
// Настройка ICE серверов
|
||||
val iceServers = listOf(
|
||||
PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer()
|
||||
)
|
||||
|
||||
peerConnection = factory.createPeerConnection(
|
||||
PeerConnection.RTCConfiguration(iceServers),
|
||||
object : PeerConnection.Observer {
|
||||
override fun onIceCandidate(candidate: IceCandidate) {
|
||||
sendIceCandidate(candidate)
|
||||
}
|
||||
// ... другие callbacks
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun startCapture() {
|
||||
val videoCapturer = Camera2Enumerator(context).run {
|
||||
deviceNames.firstOrNull()?.let { createCapturer(it, null) }
|
||||
}
|
||||
|
||||
val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", null)
|
||||
val videoSource = factory.createVideoSource(false)
|
||||
videoCapturer?.initialize(surfaceTextureHelper, context, videoSource.capturerObserver)
|
||||
|
||||
localVideoTrack = factory.createVideoTrack("local_video", videoSource)
|
||||
peerConnection?.addTrack(localVideoTrack, listOf("stream_id"))
|
||||
}
|
||||
|
||||
private fun sendIceCandidate(candidate: IceCandidate) {
|
||||
socket?.emit("webrtc-ice-candidate", JSONObject().apply {
|
||||
put("to", partnerId)
|
||||
put("candidate", candidate.toJson())
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 Сигналинг клиент
|
||||
|
||||
```kotlin
|
||||
// SignalingClient.kt
|
||||
class SignalingClient(private val serverUrl: String) {
|
||||
private var socket: Socket? = null
|
||||
private var webRTCManager: WebRTCManager? = null
|
||||
|
||||
fun connect(channel: String) {
|
||||
socket = IO.socket(serverUrl).apply {
|
||||
on(Socket.EVENT_CONNECT) {
|
||||
emit("join-channel", JSONObject().apply {
|
||||
put("channel", channel)
|
||||
put("type", "sender")
|
||||
})
|
||||
}
|
||||
|
||||
on("partner-found") { args ->
|
||||
val data = args[0] as JSONObject
|
||||
val partnerId = data.getString("partnerId")
|
||||
createOffer(partnerId)
|
||||
}
|
||||
|
||||
on("webrtc-offer") { args ->
|
||||
val data = args[0] as JSONObject
|
||||
handleOffer(data)
|
||||
}
|
||||
|
||||
connect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createOffer(partnerId: String) {
|
||||
webRTCManager?.createOffer { offer ->
|
||||
socket?.emit("webrtc-offer", JSONObject().apply {
|
||||
put("to", partnerId)
|
||||
put("offer", offer.toJson())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💻 ЭТАП 3: DESKTOP ПРИЛОЖЕНИЕ (ELECTRON)
|
||||
|
||||
### 3.1 Инициализация проекта
|
||||
|
||||
```bash
|
||||
mkdir videoreader-desktop
|
||||
cd videoreader-desktop
|
||||
npm init -y
|
||||
npm install electron react react-dom typescript
|
||||
npm install simple-peer socket.io-client --save
|
||||
```
|
||||
|
||||
### 3.2 WebRTC компонент
|
||||
|
||||
```typescript
|
||||
// src/components/VideoReceiver.tsx
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import io from 'socket.io-client';
|
||||
import SimplePeer from 'simple-peer';
|
||||
|
||||
interface VideoReceiverProps {
|
||||
channel: string;
|
||||
serverUrl: string;
|
||||
}
|
||||
|
||||
export const VideoReceiver: React.FC<VideoReceiverProps> = ({ channel, serverUrl }) => {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [peer, setPeer] = useState<SimplePeer.Instance | null>(null);
|
||||
const socket = useRef(io(serverUrl));
|
||||
|
||||
useEffect(() => {
|
||||
socket.current.emit('join-channel', {
|
||||
channel,
|
||||
type: 'receiver'
|
||||
});
|
||||
|
||||
socket.current.on('partner-found', (data: { partnerId: string }) => {
|
||||
const newPeer = new SimplePeer({
|
||||
initiator: false,
|
||||
trickle: false
|
||||
});
|
||||
|
||||
newPeer.on('signal', (signal) => {
|
||||
socket.current.emit('webrtc-answer', {
|
||||
to: data.partnerId,
|
||||
answer: signal
|
||||
});
|
||||
});
|
||||
|
||||
newPeer.on('stream', (stream) => {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.srcObject = stream;
|
||||
setConnected(true);
|
||||
}
|
||||
});
|
||||
|
||||
setPeer(newPeer);
|
||||
});
|
||||
|
||||
socket.current.on('webrtc-offer', (data: { from: string; offer: any }) => {
|
||||
if (peer) {
|
||||
peer.signal(data.offer);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.current.disconnect();
|
||||
peer?.destroy();
|
||||
};
|
||||
}, [channel, serverUrl, peer]);
|
||||
|
||||
return (
|
||||
<div className="video-receiver">
|
||||
<h2>Channel: {channel}</h2>
|
||||
<div className={`status ${connected ? 'connected' : 'disconnected'}`}>
|
||||
{connected ? 'Connected' : 'Waiting for connection...'}
|
||||
</div>
|
||||
<video
|
||||
ref={videoRef}
|
||||
autoPlay
|
||||
muted
|
||||
style={{ width: '100%', maxWidth: '800px' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 3.3 Главное окно Electron
|
||||
|
||||
```typescript
|
||||
// src/main.ts
|
||||
import { app, BrowserWindow } from 'electron';
|
||||
import * as path from 'path';
|
||||
|
||||
function createWindow() {
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
webSecurity: false // Для разработки, в продакшене нужно настроить правильно
|
||||
}
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
mainWindow.loadURL('http://localhost:3001');
|
||||
mainWindow.webContents.openDevTools();
|
||||
} else {
|
||||
mainWindow.loadFile(path.join(__dirname, '../build/index.html'));
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 ЭТАП 4: БЕЗОПАСНОСТЬ
|
||||
|
||||
### 4.1 Аутентификация пользователей
|
||||
|
||||
```typescript
|
||||
// auth/AuthService.ts
|
||||
import jwt from 'jsonwebtoken';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
export class AuthService {
|
||||
private static readonly JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
||||
|
||||
static async hashPassword(password: string): Promise<string> {
|
||||
return bcrypt.hash(password, 12);
|
||||
}
|
||||
|
||||
static async verifyPassword(password: string, hash: string): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
|
||||
static generateToken(userId: string): string {
|
||||
return jwt.sign({ userId }, this.JWT_SECRET, { expiresIn: '24h' });
|
||||
}
|
||||
|
||||
static verifyToken(token: string): any {
|
||||
return jwt.verify(token, this.JWT_SECRET);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 HTTPS и WSS
|
||||
|
||||
```nginx
|
||||
# nginx.conf
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name your-domain.com;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://signaling:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 ЭТАП 5: МОНИТОРИНГ И МАСШТАБИРОВАНИЕ
|
||||
|
||||
### 5.1 Логирование
|
||||
|
||||
```typescript
|
||||
// logger/Logger.ts
|
||||
import winston from 'winston';
|
||||
|
||||
export const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
),
|
||||
defaultMeta: { service: 'videoreader-signaling' },
|
||||
transports: [
|
||||
new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
||||
new winston.transports.File({ filename: 'combined.log' }),
|
||||
new winston.transports.Console({
|
||||
format: winston.format.simple()
|
||||
})
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### 5.2 Метрики
|
||||
|
||||
```typescript
|
||||
// metrics/Metrics.ts
|
||||
import prometheus from 'prom-client';
|
||||
|
||||
export const metrics = {
|
||||
connectionsTotal: new prometheus.Counter({
|
||||
name: 'connections_total',
|
||||
help: 'Total number of connections'
|
||||
}),
|
||||
|
||||
activeConnections: new prometheus.Gauge({
|
||||
name: 'active_connections',
|
||||
help: 'Number of active connections'
|
||||
}),
|
||||
|
||||
channelsActive: new prometheus.Gauge({
|
||||
name: 'channels_active',
|
||||
help: 'Number of active channels'
|
||||
})
|
||||
};
|
||||
|
||||
prometheus.register.registerMetric(metrics.connectionsTotal);
|
||||
prometheus.register.registerMetric(metrics.activeConnections);
|
||||
prometheus.register.registerMetric(metrics.channelsActive);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 ДЕПЛОЙMENT
|
||||
|
||||
### Kubernetes манифесты
|
||||
|
||||
```yaml
|
||||
# k8s/deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: videoreader-signaling
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: videoreader-signaling
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: videoreader-signaling
|
||||
spec:
|
||||
containers:
|
||||
- name: signaling
|
||||
image: your-registry/videoreader-signaling:latest
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
env:
|
||||
- name: REDIS_URL
|
||||
value: "redis://redis-service:6379"
|
||||
- name: JWT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: app-secrets
|
||||
key: jwt-secret
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 РЕКОМЕНДАЦИИ ПО УЛУЧШЕНИЮ
|
||||
|
||||
### 1. Производительность
|
||||
- Использование WebRTC для P2P соединений
|
||||
- CDN для статических файлов
|
||||
- Load balancing для сигналинг серверов
|
||||
- Redis Cluster для масштабирования
|
||||
|
||||
### 2. Безопасность
|
||||
- End-to-end шифрование
|
||||
- Rate limiting
|
||||
- DDoS защита
|
||||
- Регулярные security аудиты
|
||||
|
||||
### 3. Пользовательский опыт
|
||||
- Progressive Web App (PWA) версия
|
||||
- Адаптивный дизайн
|
||||
- Офлайн режим
|
||||
- Уведомления
|
||||
|
||||
### 4. Мониторинг
|
||||
- Grafana дашборды
|
||||
- Alertmanager для уведомлений
|
||||
- Jaeger для трейсинга
|
||||
- ELK стек для логов
|
||||
|
||||
---
|
||||
|
||||
## 📈 ПЛАН РАЗВИТИЯ
|
||||
|
||||
### Версия 2.0
|
||||
- [ ] Групповые видеозвонки
|
||||
- [ ] Запись и хранение видео
|
||||
- [ ] AI анализ контента
|
||||
- [ ] Мобильные push уведомления
|
||||
|
||||
### Версия 3.0
|
||||
- [ ] Облачное хранилище
|
||||
- [ ] API для интеграций
|
||||
- [ ] Белый лейбл решение
|
||||
- [ ] Международная локализация
|
||||
|
||||
---
|
||||
|
||||
**🎯 РЕЗУЛЬТАТ:** Современная, безопасная и масштабируемая система видеонаблюдения с собственной инфраструктурой, готовая к продакшн использованию и дальнейшему развитию.
|
||||
Reference in New Issue
Block a user