Sơ đồ thành phần
Browser / Mobile / Operator
|
v
Nginx TLS (vmsclient.vnso.vn)
|-- / -> Portal console SPA
|-- /v2/api, /v2/ws -> FastAPI core :28080
|-- /v2/hls -> FastAPI core tenant-checked media gateway
|-- /ai-grafana -> Protected Grafana gateway
FastAPI core
|-- Postgres 16 -> tenants, cameras, events, audit-ready metadata
|-- Redis 7 -> pub/sub, dedup keys, config notify
|-- Recordings path -> tenant/camera/YYYY/MM/DD/HH HLS segments
Worker Recorder
| |
|-- RTSP over TCP |-- FFmpeg -rtsp_transport tcp
|-- AI adapter |-- HLS playlist + segments
|-- model_version |-- retention cleanup
|-- event_id/trace_id |
v v
AI server / Triton Local NVMe, then S3/MinIO warm tier
Trách nhiệm từng service
- core
- Control plane: JWT auth, tenant-scoped cameras, events query, playback timeline, WebSocket fan-out, HLS media gateway và metrics.
- worker
- Đọc RTSP qua TCP, giảm frame, gọi AI adapter, chạy plugin stateless, dedup sự kiện và publish Redis.
- recorder
- FFmpeg HLS segmenter, rotate theo giờ, cleanup theo retention và xuất metric disk/inode cho cảnh báo.
- postgres
- Metadata source of truth: tenants, cameras, events và các cột trace/model/recording/thumbnail.
- redis
- Pub/sub realtime, dedup key, config notify. Redis Streams là nâng cấp tiếp theo khi cần replay tin cậy.
- prometheus
- Scrape core, worker, recorder và AI host; Grafana hiển thị tại gateway được bảo vệ.
Public surfaces
| Surface | Vai trò | Ghi chú |
|---|---|---|
ccai.vnso.vn | Website / landing | Giải thích giá trị, mô hình pilot và CTA liên hệ. |
vmsclient.vnso.vn | Portal console | Root luôn là console, không phải landing. |
vmsclient.vnso.vn/v2/* | Runtime v2 | API, WS, HLS, metrics và health đi qua cùng origin production. |
docs-vmsclient.vnso.vn | VMS docs | Kiến trúc, API, runbook, plugin, theme và Cloud Storage Camera AI. |
docs-license.vnso.vn | License docs | Turnstile, unified login, license portal operations. |
license.vnso.vn | License authority | V1 authority được bridge sang v2; phải có grace period khi v1 lỗi. |
Hợp đồng plugin engine
async def plugin(detections: list[Detection], cam: Camera) -> Event | None:
"""Stateless. Trả None nếu không có sự kiện."""
...
PLUGINS = {
"violence": violence_plugin,
"vehicle": vehicle_plugin,
"person": person_plugin,
}
Tenant gating
Mọi request lấy tenant_id từ JWT. API không tin tenant_id trong body, và HLS playback cũng đi qua core để kiểm tra quyền.
POST /api/cameras: chặn nếu vượtcamera_limit, lọcfeaturestheo whitelist tenant.GET /api/events: bắt buộc filter tenant, camera và event đều thuộc cùng tenant./v2/hls/*: không alias trực tiếp thư mục ghi hình; core xác thực tenant trước khi trả segment.