Agent ๊ธฐ๋ฐ ์์ ๋ชจ๋ํฐ๋ง ์์คํ - ๋ฐฑ์๋
AEGIS Backend๋ CCTV ๋ชจ๋ํฐ๋ง ์์คํ ์ REST API, ์ธ์ฆ/์ธ๊ฐ, ์ค์๊ฐ ์๋ฆผ, ๋ฏธ๋์ด ์๋ฒ ์ฐ๋์ ๋ด๋นํ๋ Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋ค.
| ๋ถ๋ฅ | ๊ธฐ์ |
|---|---|
| Framework | Spring Boot 3.5.9 |
| Language | Java 21 |
| Database | PostgreSQL, Spring Data JPA |
| Cache | Redis, Spring Data Redis |
| Security | Spring Security, JWT (jjwt 0.12) |
| Storage | AWS S3 SDK v2 (MinIO ํธํ) |
| Docs | SpringDoc OpenAPI 2.8 |
| Build | Gradle 8.x |
src/main/java/com/aegis/aegisbackend/
โโโ AegisBackendApplication.java # ๋ฉ์ธ ํด๋์ค
โโโ domain/ # ๋๋ฉ์ธ ๊ณ์ธต
โ โโโ auth/ # ์ธ์ฆ
โ โ โโโ controller/AuthController.java
โ โ โโโ dto/AuthDto.java
โ โ โโโ service/AuthService.java
โ โโโ camera/ # ์นด๋ฉ๋ผ
โ โ โโโ controller/CameraController.java
โ โ โโโ dto/CameraDto.java
โ โ โโโ entity/Camera.java
โ โ โโโ entity/UserCamera.java
โ โ โโโ repository/CameraRepository.java
โ โ โโโ repository/UserCameraRepository.java
โ โ โโโ service/CameraService.java
โ โโโ event/ # ์ด๋ฒคํธ
โ โ โโโ controller/EventController.java
โ โ โโโ controller/ActionController.java # ์ก์
์น์ธ/๊ฑฐ๋ถ API
โ โ โโโ dto/EventDto.java
โ โ โโโ entity/Event.java
โ โ โโโ entity/EventAction.java # ์ด๋ฒคํธ ์ก์
๋ก๊ทธ
โ โ โโโ repository/EventRepository.java
โ โ โโโ repository/EventActionRepository.java
โ โ โโโ repository/EventSpecification.java # ๋์ ํํฐ๋ง ์ฟผ๋ฆฌ
โ โ โโโ service/EventService.java
โ โโโ notification/ # ์๋ฆผ
โ โ โโโ controller/NotificationController.java
โ โ โโโ dto/NotificationDto.java
โ โ โโโ entity/Notification.java
โ โ โโโ repository/NotificationRepository.java
โ โ โโโ service/NotificationService.java
โ โ โโโ service/SseEmitterService.java
โ โโโ stats/ # ํต๊ณ
โ โ โโโ controller/StatsController.java
โ โ โโโ dto/StatsDto.java
โ โ โโโ service/StatsService.java
โ โโโ stream/ # ์คํธ๋ฆผ (DTO๋ง)
โ โ โโโ dto/StreamDto.java
โ โโโ user/ # ์ฌ์ฉ์
โ โโโ controller/UserController.java
โ โโโ dto/UserDto.java
โ โโโ entity/User.java
โ โโโ repository/UserRepository.java
โ โโโ service/UserService.java
โโโ global/ # ์ ์ญ ์ค์
โ โโโ common/
โ โ โโโ dto/PageResponse.java
โ โ โโโ enums/
โ โ โโโ EventRisk.java # NORMAL, SUSPICIOUS, ABNORMAL
โ โ โโโ EventStatus.java # PROCESSING, ANALYZED
โ โ โโโ EventType.java # ASSAULT, BURGLARY, DUMP, SWOON, VANDALISM
โ โ โโโ NotificationType.java # ALERT, WARNING, INFO, SUCCESS
โ โ โโโ UserRole.java # USER, ADMIN
โ โโโ config/
โ โ โโโ AsyncConfig.java # ๋น๋๊ธฐ ์ค์
โ โ โโโ DataInitializer.java # ์ด๊ธฐ Admin ์์ฑ
โ โ โโโ OpenApiConfig.java # Swagger ์ค์
โ โ โโโ RedisConfig.java # Redis ์ค์
โ โ โโโ S3Config.java # S3 ์ค์
โ โ โโโ SecurityConfig.java # Spring Security ์ค์
โ โโโ exception/
โ โ โโโ BusinessException.java
โ โ โโโ ErrorCode.java
โ โ โโโ GlobalExceptionHandler.java
โ โโโ security/
โ โโโ CustomUserDetailsService.java
โ โโโ JwtAuthenticationFilter.java
โ โโโ JwtTokenProvider.java
โโโ infra/ # ์ธํ๋ผ ๊ณ์ธต
โโโ agent/ # AI Agent ์ฐ๋
โ โโโ AgentWebhookController.java
โ โโโ dto/
โ โ โโโ CreateEventRequest.java
โ โ โโโ EventActionRequest.java
โ โ โโโ EventActionUpdateRequest.java
โ โ โโโ EventUpdateRequest.java
โ โ โโโ PendingActionResponse.java
โ โโโ service/
โ โโโ PendingActionService.java
โโโ mediamtx/ # MediaMTX ์ฐ๋
โ โโโ MediaMTXSyncService.java
โ โโโ MediaMTXWebhookController.java
โโโ redis/
โ โโโ RedisTokenService.java
โโโ s3/
โโโ S3Service.java
โโโ TempClipCleanupScheduler.java
1. MediaMTX์์ ์คํธ๋ฆผ ์์/์ข
๋ฃ ์ runOnReady/runOnNotReady ํ
์คํ
2. MediaMTX โ POST /internal/mediamtx/sync ํธ์ถ
3. MediaMTXSyncService:
- GET /v3/paths/list๋ก MediaMTX API์์ ์คํธ๋ฆผ ๋ชฉ๋ก ์กฐํ
- DB์ ์นด๋ฉ๋ผ ๋ชฉ๋ก๊ณผ ๋น๊ต
- ์ ์คํธ๋ฆผ: INSERT (connected=true, enabled=false)
- ๊ธฐ์กด ์คํธ๋ฆผ: UPDATE connected ์ํ
- ์ฌ๋ผ์ง ์คํธ๋ฆผ: UPDATE connected=false
4. analysisEnabled=true์ธ ์นด๋ฉ๋ผ ๋ชฉ๋ก์ Redis์ ์ ์ฅ (analysis:cameras)
5. Redis Pub/Sub์ผ๋ก "camera:analysis:update" ์ฑ๋์ "sync" ๋ฐํ
6. SSE๋ก ํ๋ก ํธ์๋์ "camera" ์ด๋ฒคํธ ๋ธ๋ก๋์บ์คํธ
1. AI Agent โ POST /internal/agent/events (1์ฐจ ๋ถ์ ๊ฒฐ๊ณผ)
- Request: { cameraId, risk, type, occurredAt }
- ์ด๋ฒคํธ ์์ฑ (status=PROCESSING)
- ์๋ฆผ ์์ฑ (risk ๊ธฐ๋ฐ ํ์
: abnormalโALERT, suspiciousโWARNING, normalโINFO)
- SSE ๋ธ๋ก๋์บ์คํธ (event)
- Response: { eventId }
2. AI Agent โ GET /internal/agent/events/{id}/clip/upload-url
- S3 presigned PUT URL ์์ฑ (clips/{eventId}.mp4, 10๋ถ ๋ง๋ฃ)
- Response: { uploadUrl }
3. AI Agent โ presigned URL๋ก MinIO์ ์ง์ ์
๋ก๋
4. AI Agent โ POST /internal/agent/events/{id}/clip/confirm
- S3์์ ํด๋ฆฝ ์กด์ฌ ํ์ธ (clips/{eventId}.mp4)
- Event.clipUrl ์ ์ฅ
- SSE ๋ธ๋ก๋์บ์คํธ (event)
5. AI Agent โ PATCH /internal/agent/events/{id} (2์ฐจ ๋ถ์ ๊ฒฐ๊ณผ)
- Request: { risk, type, summary, report, status }
- null์ด ์๋ ํ๋๋ง ์
๋ฐ์ดํธ
- ์๋ฆผ ์์ฑ (์์ ๋ risk ๊ธฐ๋ฐ ํ์
)
- SSE ๋ธ๋ก๋์บ์คํธ (event)
6. AI Agent โ POST /internal/agent/events/{id}/actions (์ก์
๊ธฐ๋ก)
- Request: { action, description }
- ์ก์
๋ก๊ทธ ์ ์ฅ
- ์๋ฆผ ์์ฑ (์ด๋ฒคํธ risk ๊ธฐ๋ฐ ํ์
)
- Response: { actionId }
7. AI Agent โ PATCH /internal/agent/events/{id}/actions/{actionId} (์ก์
์์ )
- Request: { userId (์ ํ), action, description }
- userId๊ฐ ์์ผ๋ฉด ์น์ธ/๊ฑฐ์ ์ฌ์ฉ์ ์
๋ฐ์ดํธ
- ์๋ฆผ ์์ฑ (์ด๋ฒคํธ risk ๊ธฐ๋ฐ ํ์
)
- Response: { actionId }
8. AI Agent โ POST /internal/agent/events/{id}/actions/{actionId}/pending (Human-in-the-Loop)
- Request: (์์)
- DeferredResult๋ก ์๋ต ํ๋ฉ
- ์๋ฆผ ์์ฑ (์ด๋ฒคํธ risk ๊ธฐ๋ฐ ํ์
)
- SSE "action-pending" ๋ธ๋ก๋์บ์คํธ
- ์ฌ์ฉ์ ์น์ธ/๊ฑฐ๋ถ ์ ์๋ต ๋ฐํ
- Response: { userId, userName, userEmail, result }
AI Agent Spring Frontend
| | |
|-- POST /actions ------------>| |
|<-- { actionId } -------------| |
| | |
|-- POST /actions/{id}/pending->| |
| (์๋ต ํ๋ฉ) |-- SSE "action-pending" ------->|
| | |
| | [์น์ธ/๊ฑฐ๋ถ UI ํ์] |
| | |
| |<-- POST /actions/{id}/resolve -|
| | { approved: true/false } |
| | |
|<-- { userId, userName, ------|-- SSE "action-resolved" ------>|
| userEmail, result } | |
1. ๋ธ๋ผ์ฐ์ โ POST /stream/{camera}/whep (via Caddy)
- Authorization: Basic base64("_:" + accessToken)
2. MediaMTX โ POST /internal/mediamtx/auth (์ธ์ฆ ์์)
- Request: { user, password, action, path, protocol, ip }
3. MediaMTXWebhookController:
- protocol๋ณ ๋ถ๊ธฐ:
- SRT publish: ID/PW ๊ฒ์ฆ
- WebRTC read: password ํ๋์ JWT ๊ฒ์ฆ + ์นด๋ฉ๋ผ ๊ถํ ํ์ธ
- RTSP/HLS read: ์ธ์ฆ ์์ (๋ด๋ถ๋ง)
- ์ฑ๊ณต: 200 OK
- ์คํจ: 401 Unauthorized
4. ์ธ์ฆ ์ฑ๊ณต ์ ๋ธ๋ผ์ฐ์ โ MediaMTX WebRTC ์ฐ๊ฒฐ ์๋ฆฝ
1. ๋ก๊ทธ์ธ (POST /api/auth/login):
- ์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ ๊ฒ์ฆ
- Access Token ์์ฑ (15๋ถ)
- Refresh Token ์์ฑ (7์ผ)
- Redis์ Refresh Token ์ ์ฅ (key: refresh_token:{token}, value: userId)
- Cookie์ Refresh Token ์ค์ (HttpOnly, Secure)
- Response: { accessToken, user }
2. API ์์ฒญ:
- Authorization: Bearer {accessToken}
- JwtAuthenticationFilter์์ JWT ๊ฒ์ฆ
- SecurityContext์ ์ธ์ฆ ์ ๋ณด ์ค์
3. ํ ํฐ ๊ฐฑ์ (POST /api/auth/refresh):
- Cookie์์ Refresh Token ์ถ์ถ
- Redis์์ userId ์กฐํ
- ์ Access Token ๋ฐ๊ธ
- Response: { accessToken }
4. ๋ก๊ทธ์์ (POST /api/auth/logout):
- Redis์์ Refresh Token ์ญ์
- Cookie ์ญ์
1. ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ (GET /api/notifications/stream):
- SseEmitter ์์ฑ (ํ์์์: 30๋ถ)
- ์ฌ์ฉ์๋ณ Map์ ์ ์ฅ
- "connect" ์ด๋ฒคํธ ์ ์ก
- ํ์ฌ pending ์ก์
๋ชฉ๋ก ์ ์ก
2. ์ด๋ฒคํธ ๋ฐ์ ์:
- NotificationService: DB์ ์๋ฆผ ์ ์ฅ
- SseEmitterService.broadcast*(): ๋ชจ๋ ์ฐ๊ฒฐ๋ ํด๋ผ์ด์ธํธ์ ์ ์ก
3. ์ด๋ฒคํธ ํ์
:
- notification: ์ ์๋ฆผ (ํ ์คํธ ํ์)
- camera: ์นด๋ฉ๋ผ ์ํ ๋ณ๊ฒฝ
- event: ์ด๋ฒคํธ ์์ฑ/์์
- event-deleted: ์ด๋ฒคํธ ์ญ์
- member: ๋ฉค๋ฒ ๋ณ๊ฒฝ
- action-update: ์ก์
์์ฑ/์์ (๋ชจ๋ฌ ๊ฐฑ์ )
- action-pending: ์ก์
์น์ธ ๋๊ธฐ (Human-in-the-Loop)
- action-resolved: ์ก์
์น์ธ/๊ฑฐ๋ถ ์๋ฃ
4. ์ฐ๊ฒฐ ์ข
๋ฃ/์ค๋ฅ ์:
- Map์์ Emitter ์ ๊ฑฐ
- ํด๋ผ์ด์ธํธ ์ฌ์ฐ๊ฒฐ ํ์
| ๋ฉ์๋ | ๊ธฐ๋ฅ | ํน์ด์ฌํญ |
|---|---|---|
signup() |
ํ์๊ฐ์ | approved=false๋ก ์์ฑ, ๊ด๋ฆฌ์ ์น์ธ ํ์ |
login() |
๋ก๊ทธ์ธ | approved/deleted ๊ฒ์ฆ, Refresh Token Redis ์ ์ฅ |
logout() |
๋ก๊ทธ์์ | Redis์์ Refresh Token ์ญ์ |
refresh() |
ํ ํฐ ๊ฐฑ์ | Cookie์ Refresh Token์ผ๋ก Access Token ์ฌ๋ฐ๊ธ |
changePassword() |
๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ | ํ์ฌ ๋น๋ฐ๋ฒํธ ํ์ธ ํ ๋ณ๊ฒฝ |
deleteAccount() |
ํ์ ํํด | ์ํํธ ์ญ์ (deleted=true, deletedAt ๊ธฐ๋ก) |
| ๋ฉ์๋ | ๊ธฐ๋ฅ | ํน์ด์ฌํญ |
|---|---|---|
getCamerasPaged() |
์นด๋ฉ๋ผ ๋ชฉ๋ก | ๊ถํ๋ณ ํํฐ๋ง, ์ ๋ ฌ: connectedโenabledโlocation |
updateCamera() |
์นด๋ฉ๋ผ ์์ | location, enabled, analysisEnabled ์์ ๊ฐ๋ฅ |
syncAnalysisCamerasToRedis() |
Redis ๋๊ธฐํ | analysisEnabled=true ์นด๋ฉ๋ผ๋ฅผ Redis์ ์ ์ฅ |
| ๋ฉ์๋ | ๊ธฐ๋ฅ | ํน์ด์ฌํญ |
|---|---|---|
getEventsPaged() |
์ด๋ฒคํธ ๋ชฉ๋ก | ๊ถํ๋ณ ํํฐ๋ง (Admin: ์ ์ฒด, User: ํ ๋น ์นด๋ฉ๋ผ๋ง) |
deleteEvent() |
์ด๋ฒคํธ ์ญ์ | S3 ํด๋ฆฝ ์ญ์ โ ์๋ฆผ ์ญ์ โ ์ด๋ฒคํธ ์ญ์ โ SSE ๋ธ๋ก๋์บ์คํธ |
| ๋ฉ์๋ | ๊ธฐ๋ฅ | ํน์ด์ฌํญ |
|---|---|---|
syncCameras() |
์นด๋ฉ๋ผ ๋๊ธฐํ | Redis Lock์ผ๋ก ์ค๋ณต ์คํ ๋ฐฉ์ง (1์ด TTL) |
| ๋ฉ์๋ | ๊ธฐ๋ฅ | ํน์ด์ฌํญ |
|---|---|---|
generateUploadUrl() |
์ ๋ก๋ URL ์์ฑ | clips/{eventId}.mp4, 10๋ถ ๋ง๋ฃ |
generateDownloadUrl() |
๋ค์ด๋ก๋ URL ์์ฑ | Caddy ๋๋ฉ์ธ์ผ๋ก ์๋ช |
clipExists() |
ํด๋ฆฝ ์กด์ฌ ํ์ธ | clips/{eventId}.mp4 ํ์ธ |
downloadClip() |
ํด๋ฆฝ ๋ค์ด๋ก๋ | byte[] ๋ฐํ |
deleteClip() |
ํด๋ฆฝ ์ญ์ | ์ด๋ฒคํธ ์ญ์ ์ ํธ์ถ |
cleanupTempClips() |
์์ ํด๋ฆฝ ์ ๋ฆฌ | ์ค์ผ์ค๋ฌ์์ ํธ์ถ (ํ์ฌ temp/clips ๋ฏธ์ฌ์ฉ) |
# ๋น๋
./gradlew build
# ์คํ
./gradlew bootRun
# ํ
์คํธ
./gradlew testapplication.properties ๋๋ ํ๊ฒฝ ๋ณ์๋ก ์ค์ :
| ๋ณ์ | ์ค๋ช | ๊ธฐ๋ณธ๊ฐ |
|---|---|---|
DB_URL |
PostgreSQL URL | jdbc:postgresql://localhost:5432/aegis |
DB_USERNAME |
DB ์ฌ์ฉ์ | aegis |
DB_PASSWORD |
DB ๋น๋ฐ๋ฒํธ | trillion |
REDIS_HOST |
Redis ํธ์คํธ | localhost |
REDIS_PORT |
Redis ํฌํธ | 6379 |
REDIS_PASSWORD |
Redis ๋น๋ฐ๋ฒํธ | (๋น ๋ฌธ์์ด) |
AWS_S3_ACCESS_KEY |
S3 Access Key | aegis |
AWS_S3_SECRET_KEY |
S3 Secret Key | trillion |
AWS_S3_REGION |
S3 ๋ฆฌ์ | us-east-1 |
AWS_S3_BUCKET |
S3 ๋ฒํท | aegis |
AWS_S3_ENDPOINT |
S3 ์๋ํฌ์ธํธ (MinIO์ฉ) | http://localhost:9000 |
JWT_SECRET |
JWT ์๋ช ํค (256bit ์ด์) | (๊ฐ๋ฐ์ฉ ๊ธฐ๋ณธ๊ฐ) |
JWT_ACCESS_EXPIRATION |
Access Token ๋ง๋ฃ (ms) | 900000 (15๋ถ) |
JWT_REFRESH_EXPIRATION |
Refresh Token ๋ง๋ฃ (ms) | 604800000 (7์ผ) |
MEDIAMTX_API_URL |
MediaMTX API URL | http://localhost:9997 |
MEDIAMTX_WEBRTC_URL |
WebRTC WHEP ๊ธฐ๋ณธ ๊ฒฝ๋ก | /stream |
MEDIAMTX_SRT_USER |
SRT ์ธ์ฆ ์ฌ์ฉ์ | aegis |
MEDIAMTX_SRT_PASSWORD |
SRT ์ธ์ฆ ๋น๋ฐ๋ฒํธ | trillion |
ADMIN_EMAIL |
์ด๊ธฐ Admin ์ด๋ฉ์ผ | admin@aegis.local |
ADMIN_PASSWORD |
์ด๊ธฐ Admin ๋น๋ฐ๋ฒํธ | changeyourpassword |
ADMIN_NAME |
์ด๊ธฐ Admin ์ด๋ฆ | Admin |
| Method | Path | ์ค๋ช |
|---|---|---|
| POST | /signup |
ํ์๊ฐ์ |
| POST | /login |
๋ก๊ทธ์ธ |
| POST | /logout |
๋ก๊ทธ์์ |
| POST | /refresh |
ํ ํฐ ๊ฐฑ์ |
| GET | /me |
๋ด ์ ๋ณด ์กฐํ |
| PATCH | /me |
ํ๋กํ ์์ |
| PATCH | /password |
๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ |
| DELETE | /me |
ํ์ ํํด |
Request:
{
"email": "string (ํ์, ์ด๋ฉ์ผ ํ์)",
"password": "string (ํ์, 6์ ์ด์)",
"name": "string (ํ์, 100์ ์ดํ)"
}Response: 200 OK
{
"success": true,
"message": "ํ์๊ฐ์
์ด ์๋ฃ๋์์ต๋๋ค. ๊ด๋ฆฌ์ ์น์ธ ํ ๋ก๊ทธ์ธ์ด ๊ฐ๋ฅํฉ๋๋ค."
}Request:
{
"email": "string (ํ์)",
"password": "string (ํ์)"
}Response: 200 OK
{
"accessToken": "JWT ํ ํฐ",
"user": {
"id",
"email",
"name",
"role",
"assignedCameras",
"createdAt",
"approved"
}
}Cookie: refreshToken (HttpOnly, Secure, 7์ผ)
Cookie: refreshToken ํ์
Response: 200 OK
{
"accessToken": "์ JWT ํ ํฐ"
}Request:
{
"currentPassword": "string (ํ์)",
"newPassword": "string (ํ์, 6์ ์ด์)"
}| Method | Path | ์ค๋ช |
|---|---|---|
| GET | / |
์นด๋ฉ๋ผ ๋ชฉ๋ก (ํ์ด์ง๋ค์ด์ , ๊ธฐ๋ณธ size=6) |
| GET | /all |
์นด๋ฉ๋ผ ์ ์ฒด ๋ชฉ๋ก (๋ฉค๋ฒ ๊ด๋ฆฌ - ์นด๋ฉ๋ผ ํ ๋น์ฉ) |
| GET | /{id} |
์นด๋ฉ๋ผ ์์ธ |
| PATCH | /{id} |
์นด๋ฉ๋ผ ์์ |
์ ๋ ฌ ์์: connected DESC โ enabled DESC โ location ASC
Response: 200 OK (PageResponse)
{
"content": [
{
"id": "UUID",
"name": "์นด๋ฉ๋ผ๋ช
(MediaMTX ์๋ณธ)",
"location": "์ฅ์",
"connected": true,
"enabled": true,
"analysisEnabled": true,
"streamUrl": "/stream/{name}/whep"
}
],
"page": 0,
"size": 6,
"totalElements": 10,
"totalPages": 2,
"first": true,
"last": false
}Response: 200 OK
[
{
"id": "UUID",
"name": "์นด๋ฉ๋ผ๋ช
",
"location": "์ฅ์",
"connected": true,
"enabled": true,
"analysisEnabled": true,
"streamUrl": "/stream/{name}/whep"
}
]Response: 200 OK
{
"id": "UUID",
"name": "์นด๋ฉ๋ผ๋ช
",
"location": "์ฅ์",
"connected": true,
"enabled": true,
"analysisEnabled": true,
"streamUrl": "/stream/{name}/whep"
}Request:
{
"location": "string (์ ํ)",
"enabled": "boolean (์ ํ)",
"analysisEnabled": "boolean (์ ํ)"
}Response: 200 OK
{
"id": "UUID",
"name": "์นด๋ฉ๋ผ๋ช
",
"location": "์ฅ์",
"connected": true,
"enabled": true,
"analysisEnabled": true,
"streamUrl": "/stream/{name}/whep"
}| Method | Path | ์ค๋ช |
|---|---|---|
| GET | / |
์ด๋ฒคํธ ๋ชฉ๋ก (ํ์ด์ง๋ค์ด์ , ๊ธฐ๋ณธ size=20) |
| GET | /{id} |
์ด๋ฒคํธ ์์ธ |
| GET | /{id}/report |
๋ณด๊ณ ์ HTML ์กฐํ |
| DELETE | /{id} |
์ด๋ฒคํธ ์ญ์ (Admin) |
| GET | /{id}/clip-url |
ํด๋ฆฝ ์ฌ์์ฉ presigned URL |
| GET | /{id}/clip/download-url |
ํด๋ฆฝ ๋ค์ด๋ก๋์ฉ presigned URL |
| POST | /{id}/actions/{actionId}/resolve |
์ก์ ์น์ธ/๊ฑฐ๋ถ (Human-in-the-Loop) |
Query Parameters:
| ํ๋ผ๋ฏธํฐ | ํ์ | ์ค๋ช |
|---|---|---|
page |
int | ํ์ด์ง ๋ฒํธ (๊ธฐ๋ณธ: 0) |
size |
int | ํ์ด์ง ํฌ๊ธฐ (๊ธฐ๋ณธ: 20) |
risks |
string[] | ์ํ๋ ํํฐ (suspicious, abnormal) |
types |
string[] | ์ด์ํ๋ ์ ํ (assault, burglary, dump, swoon, vandalism) |
statuses |
string[] | ๋ถ์ ์ํ (processing, analyzed) |
cameraIds |
string[] | ์นด๋ฉ๋ผ ID ๋ชฉ๋ก |
startDate |
datetime | ์์ ๋ ์ง (ISO 8601) |
endDate |
datetime | ์ข ๋ฃ ๋ ์ง (ISO 8601) |
_empty๋ง์ปค๋ฅผ ์ ์กํ๋ฉด ํด๋น ํํฐ์์ ๊ฒฐ๊ณผ ์์ ๋ฐํ (์ ์ฒด ํด์ )
Response: 200 OK (PageResponse)
{
"content": [
{
"id": "UUID",
"cameraId": "UUID",
"cameraName": "์นด๋ฉ๋ผ๋ช
",
"cameraLocation": "์ค์น ์ฅ์",
"risk": "normal | suspicious | abnormal",
"type": "assault | burglary | dump | swoon | vandalism",
"occurredAt": "2026-01-31T12:00:00",
"status": "processing | analyzed",
"clipUrl": "S3 URL (nullable)",
"summary": "AI ์์ฝ (nullable)",
"actions": "[{...}] (nullable)",
"report": "์์ธ ๋ณด๊ณ ์ (nullable)"
}
],
"page": 0,
"size": 20,
"totalElements": 100,
"totalPages": 5,
"first": true,
"last": false
}Response: 200 OK (Content-Type: text/html)
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<!-- AI Agent๊ฐ ์์ฑํ ๋ถ์ ๋ณด๊ณ ์ HTML -->
</body>
</html>Error: 404 Not Found (๋ณด๊ณ ์๊ฐ ์๋ ๊ฒฝ์ฐ)
ํด๋ฆฝ ์ฌ์์ฉ presigned URL ๋ฐํ
Response: 200 OK
{
"url": "https://... (presigned URL)"
}Error: 404 Not Found (ํด๋ฆฝ์ด ์๋ ๊ฒฝ์ฐ)
ํด๋ฆฝ ๋ค์ด๋ก๋์ฉ presigned URL ๋ฐํ
Response: 200 OK
{
"url": "https://... (presigned URL)",
"filename": "event_{id}.mp4"
}Error: 404 Not Found (ํด๋ฆฝ์ด ์๋ ๊ฒฝ์ฐ)
์ก์ ์น์ธ/๊ฑฐ๋ถ (Human-in-the-Loop)
Request:
{
"approved": boolean
}Response: 200 OK
{
"success": true
}Error: 400 Bad Request (์ก์
์ ์ฐพ์ ์ ์๊ฑฐ๋ ์ด๋ฏธ ์ฒ๋ฆฌ๋จ)
| Method | Path | ์ค๋ช |
|---|---|---|
| GET | /stream |
SSE ์ฐ๊ฒฐ |
| GET | / |
์๋ฆผ ๋ชฉ๋ก |
| DELETE | / |
์ ์ฒด ์ญ์ |
Response: 200 OK
[
{
"id": "UUID",
"type": "alert | warning | info | success",
"title": "์๋ฆผ ์ ๋ชฉ",
"message": "์๋ฆผ ๋ฉ์์ง",
"timestamp": "2026-01-31T12:00:00",
"eventId": "UUID (nullable)"
}
]Response: 200 OK
{
"success": true
}| Method | Path | ์ค๋ช |
|---|---|---|
| GET | / |
์ ์ฒด ํต๊ณ (type ๋ฏธ์ง์ ์) |
| GET | /?type=daily |
์ผ๋ณ ํต๊ณ (์ฃผ๊ฐ) |
| GET | /?type=event-types |
์ ํ๋ณ ํต๊ณ |
| GET | /?type=monthly |
์๋ณ ํต๊ณ |
Response: 200 OK
[
{
"day": "์ผ",
"events": 5,
"resolved": 3
},
{
"day": "์",
"events": 8,
"resolved": 6
}
]Response: 200 OK
[
{
"type": "assault",
"count": 10,
"color": "#ef4444"
},
{
"type": "burglary",
"count": 5,
"color": "#f97316"
}
]Response: 200 OK
{
"2026-01-15": {
"events": 5,
"alerts": 2
},
"2026-01-16": {
"events": 3,
"alerts": 1
}
}| Method | Path | ์ค๋ช |
|---|---|---|
| GET | / |
์น์ธ๋ ์ฌ์ฉ์ ๋ชฉ๋ก (์ต์ ๊ฐ์ ์) |
| GET | /pending |
๋ฏธ์น์ธ ์ฌ์ฉ์ ๋ชฉ๋ก (์ต์ ๊ฐ์ ์) |
| GET | /pending/count |
๋ฏธ์น์ธ ์ฌ์ฉ์ ์ |
| GET | /{id} |
์ฌ์ฉ์ ์์ธ |
| PATCH | /{id} |
์ฌ์ฉ์ ์์ (์ด๋๋ฏผ์ ์นด๋ฉ๋ผ ๊ถํ ์์ ๋ถ๊ฐ) |
| DELETE | /{id} |
์ฌ์ฉ์ ์ญ์ |
| PATCH | /{id}/approve |
์ฌ์ฉ์ ์น์ธ |
์น์ธ๋ ์ฌ์ฉ์ ๋ชฉ๋ก ์กฐํ (์ต์ ๊ฐ์ ์ ์ ๋ ฌ)
Response: 200 OK (PageResponse)
{
"content": [
{
"id": "UUID",
"email": "user@example.com",
"name": "์ฌ์ฉ์๋ช
",
"role": "user | admin",
"approved": true,
"assignedCameras": [
"UUID ๋ฐฐ์ด"
],
"createdAt": "2026-01-31T12:00:00"
}
],
"page": 0,
"size": 20,
"totalElements": 10,
"totalPages": 1,
"first": true,
"last": true
}Response: 200 OK
{
"id": "UUID",
"email": "user@example.com",
"name": "์ฌ์ฉ์๋ช
",
"role": "user | admin",
"approved": true,
"assignedCameras": [
"UUID ๋ฐฐ์ด"
],
"createdAt": "2026-01-31T12:00:00"
}์ด๋๋ฏผ ์ฌ์ฉ์๋ ์นด๋ฉ๋ผ ๊ถํ(assignedCameras) ์์ ๋ถ๊ฐ
Request:
{
"name": "string (์ ํ)",
"role": "user | admin (์ ํ)",
"assignedCameras": [
"์นด๋ฉ๋ผ UUID ๋ฐฐ์ด (์ ํ)"
]
}Response: 200 OK
{
"id": "UUID",
"email": "user@example.com",
"name": "์ฌ์ฉ์๋ช
",
"role": "user | admin",
"approved": true,
"assignedCameras": [
"UUID ๋ฐฐ์ด"
],
"createdAt": "2026-01-31T12:00:00"
}Response: 200 OK
{
"success": true
}Response: 200 OK
{
"id": "UUID",
"email": "user@example.com",
"name": "์ฌ์ฉ์๋ช
",
"role": "user | admin",
"approved": true,
"assignedCameras": [
"UUID ๋ฐฐ์ด"
],
"createdAt": "2026-01-31T12:00:00"
}| Method | Path | ์ค๋ช |
|---|---|---|
| POST | /events |
์ด๋ฒคํธ ์์ฑ |
| PATCH | /events/{id} |
์ด๋ฒคํธ ์์ |
| GET | /events/{id}/clip/upload-url |
ํด๋ฆฝ ์ ๋ก๋ URL ๋ฐ๊ธ |
| POST | /events/{id}/clip/confirm |
ํด๋ฆฝ ์ ๋ก๋ ์๋ฃ ํ์ธ |
| POST | /events/{id}/actions |
์ด๋ฒคํธ ์ก์ ์์ฑ |
| PATCH | /events/{id}/actions/{actionId} |
์ด๋ฒคํธ ์ก์ ์์ |
| POST | /events/{id}/actions/{actionId}/pending |
์ก์ ์น์ธ ๋๊ธฐ (Human-in-the-Loop) |
Request:
{
"cameraId": "UUID (ํ์)",
"risk": "normal | suspicious | abnormal (ํ์)",
"type": "assault | burglary | dump | swoon | vandalism (ํ์)",
"occurredAt": "ISO8601 (์ ํ, ๊ธฐ๋ณธ now)"
}Response: 201 Created
{
"eventId": "UUID"
}clips/{eventId}.mp4 ์กด์ฌ ํ์ธ ํ Event.clipUrl ์ ์ฅ
Request: Body ์์
Response: 200 OK
{
"clipUrl": "clips/{eventId}.mp4"
}Request:
{
"risk": "normal | suspicious | abnormal (์ ํ)",
"type": "assault | burglary | dump | swoon | vandalism (์ ํ)",
"summary": "string (์ ํ)",
"report": "string (์ ํ)",
"status": "processing | analyzed (์ ํ)"
}Response: 200 OK
{
"eventId": "UUID"
}ํด๋ฆฝ ์ ๋ก๋๋ฅผ ์ํ S3 presigned URL ๋ฐ๊ธ
Request: Body ์์
Response: 200 OK
{
"uploadUrl": "https://..."
}์ด๋ฒคํธ ์ก์ ์์ฑ
Request:
{
"action": "string (ํ์) - ์ก์
์ข
๋ฅ",
"description": "string (ํ์) - ์ก์
์ค๋ช
"
}Response: 201 Created
{
"actionId": "UUID"
}์ด๋ฒคํธ ์ก์ ์์
Request:
{
"userId": "UUID (์ ํ) - ์น์ธ/๊ฑฐ์ ํ ์ฌ์ฉ์",
"action": "string (ํ์)",
"description": "string (ํ์)"
}Response: 200 OK
{
"actionId": "UUID"
}์ก์ ์น์ธ ๋๊ธฐ (Human-in-the-Loop)
DeferredResult๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์ ์๋ต๊น์ง HTTP ์ฐ๊ฒฐ ์ ์ง
Request: Body ์์
Response: 200 OK (์ฌ์ฉ์ ์น์ธ/๊ฑฐ๋ถ ํ ๋ฐํ)
{
"userId": "UUID",
"userName": "string",
"userEmail": "string",
"result": true/false
}| Method | Path | ์ค๋ช |
|---|---|---|
| POST | /sync |
์นด๋ฉ๋ผ ๋๊ธฐํ ํธ๋ฆฌ๊ฑฐ |
| POST | /auth |
์คํธ๋ฆผ ์ธ์ฆ (MediaMTX ํธ์ถ) |
MediaMTX์์ ํธ์ถํ๋ ์ธ์ฆ ์์ฒญ:
Request:
{
"user": "์ฌ์ฉ์๋ช
",
"password": "๋น๋ฐ๋ฒํธ ๋๋ JWT",
"action": "publish | read",
"path": "์นด๋ฉ๋ผ ๊ฒฝ๋ก",
"protocol": "srt | rtsp | hls | webrtc",
"ip": "ํด๋ผ์ด์ธํธ IP"
}์ธ์ฆ ๊ท์น:
SRT publish: ID/PW ๊ฒ์ฆWebRTC read: JWT ๊ฒ์ฆ + ์นด๋ฉ๋ผ ๊ถํ ํ์ธRTSP/HLS read: ์ธ์ฆ ์์ (๋ด๋ถ๋ง)
erDiagram
users ||--o{ user_cameras: has
users ||--o{ notifications: receives
users ||--o{ event_actions: performs
cameras ||--o{ user_cameras: assigned_to
cameras ||--o{ events: generates
events ||--o{ event_actions: has
events ||--o{ notifications: triggers
users {
UUID id PK
VARCHAR email UK
VARCHAR password
VARCHAR name
ENUM role
BOOLEAN approved
BOOLEAN deleted
TIMESTAMP deleted_at
TIMESTAMP created_at
TIMESTAMP updated_at
}
cameras {
UUID id PK
VARCHAR name
VARCHAR location
BOOLEAN connected
BOOLEAN enabled
BOOLEAN analysis_enabled
TIMESTAMP created_at
TIMESTAMP updated_at
}
user_cameras {
UUID id PK
UUID user_id FK
UUID camera_id FK
}
events {
UUID id PK
UUID camera_id FK
ENUM risk
ENUM type
ENUM status
TIMESTAMP occurred_at
TEXT clip_url
TEXT summary
VARCHAR risk_score
JSONB rag_references
TEXT report
TIMESTAMP created_at
TIMESTAMP updated_at
}
event_actions {
UUID id PK
UUID event_id FK
UUID user_id FK
TEXT action
TEXT description
TIMESTAMP created_at
TIMESTAMP updated_at
}
notifications {
UUID id PK
UUID user_id FK
UUID event_id FK
ENUM type
VARCHAR title
TEXT message
TIMESTAMP created_at
}
| ์ปฌ๋ผ | ํ์ | ์ ์ฝ์กฐ๊ฑด | ๊ธฐ๋ณธ๊ฐ | ์ค๋ช |
|---|---|---|---|---|
| id | UUID | PK | auto | ๊ณ ์ ์๋ณ์ |
| VARCHAR(255) | UNIQUE, NOT NULL | - | ๋ก๊ทธ์ธ ์ด๋ฉ์ผ | |
| password | VARCHAR(255) | NOT NULL | - | BCrypt ์ํธํ |
| name | VARCHAR(100) | NOT NULL | - | ์ฌ์ฉ์ ์ด๋ฆ |
| role | ENUM | NOT NULL | USER | USER, ADMIN |
| approved | BOOLEAN | NOT NULL | false | ๊ด๋ฆฌ์ ์น์ธ ์ฌ๋ถ |
| deleted | BOOLEAN | NOT NULL | false | ํํด ์ฌ๋ถ |
| deleted_at | TIMESTAMP | - | NULL | ํํด ์ผ์ |
| created_at | TIMESTAMP | NOT NULL | auto | ๊ฐ์ ์ผ |
| updated_at | TIMESTAMP | NOT NULL | auto | ์์ ์ผ |
| ์ปฌ๋ผ | ํ์ | ์ ์ฝ์กฐ๊ฑด | ๊ธฐ๋ณธ๊ฐ | ์ค๋ช |
|---|---|---|---|---|
| id | UUID | PK | auto | ๊ณ ์ ์๋ณ์ |
| name | VARCHAR(50) | NOT NULL | - | MediaMTX ์คํธ๋ฆผ ๊ฒฝ๋ก๋ช |
| location | VARCHAR(100) | NOT NULL | =name | ์ฌ์ฉ์ ์ง์ ์ฅ์ |
| connected | BOOLEAN | NOT NULL | false | MediaMTX ์ฐ๊ฒฐ ์ํ |
| enabled | BOOLEAN | NOT NULL | false | ์นด๋ฉ๋ผ ํ์ฑํ |
| analysis_enabled | BOOLEAN | NOT NULL | false | AI ๋ถ์ ํ์ฑํ |
| created_at | TIMESTAMP | NOT NULL | auto | ์์ฑ์ผ |
| updated_at | TIMESTAMP | NOT NULL | auto | ์์ ์ผ |
| ์ปฌ๋ผ | ํ์ | ์ ์ฝ์กฐ๊ฑด | ์ค๋ช |
|---|---|---|---|
| id | UUID | PK | ๊ณ ์ ์๋ณ์ |
| user_id | UUID | FK โ users.id | ์ฌ์ฉ์ |
| camera_id | UUID | FK โ cameras.id | ์นด๋ฉ๋ผ |
| ์ปฌ๋ผ | ํ์ | ์ ์ฝ์กฐ๊ฑด | ๊ธฐ๋ณธ๊ฐ | ์ค๋ช |
|---|---|---|---|---|
| id | UUID | PK | auto | ๊ณ ์ ์๋ณ์ |
| camera_id | UUID | FK โ cameras.id | - | ์นด๋ฉ๋ผ |
| risk | ENUM | NOT NULL | - | NORMAL, SUSPICIOUS, ABNORMAL |
| type | ENUM | NOT NULL | - | ASSAULT, BURGLARY, DUMP, SWOON, VANDALISM |
| status | ENUM | NOT NULL | PROCESSING | PROCESSING, ANALYZED |
| occurred_at | TIMESTAMP | NOT NULL | - | ๋ฐ์ ์๊ฐ |
| clip_url | TEXT | - | NULL | S3 ํด๋ฆฝ URL |
| summary | TEXT | - | NULL | AI ์์ฝ |
| risk_score | VARCHAR(10) | - | NULL | ์ํ ์ ์ |
| rag_references | JSONB | - | NULL | RAG ์ฐธ์กฐ |
| report | TEXT | - | NULL | ์์ธ ๋ณด๊ณ ์ |
| created_at | TIMESTAMP | NOT NULL | auto | ์์ฑ์ผ |
| updated_at | TIMESTAMP | NOT NULL | auto | ์์ ์ผ |
| ์ปฌ๋ผ | ํ์ | ์ ์ฝ์กฐ๊ฑด | ๊ธฐ๋ณธ๊ฐ | ์ค๋ช |
|---|---|---|---|---|
| id | UUID | PK | auto | ๊ณ ์ ์๋ณ์ |
| event_id | UUID | FK โ events.id, NOT NULL | - | ์ด๋ฒคํธ |
| user_id | UUID | FK โ users.id | NULL | ์ก์ ์น์ธ/๊ฑฐ์ ํ ์ฌ์ฉ์ (null์ด๋ฉด ์๋) |
| action | TEXT | NOT NULL | - | ์ก์ ์ข ๋ฅ ๋๋ ์ด๋ฆ |
| description | TEXT | NOT NULL | - | ์ก์ ์คํ ๊ฒฐ๊ณผ ๋๋ ์ค๋ช |
| created_at | TIMESTAMP | NOT NULL | auto | ์์ฑ์ผ |
| updated_at | TIMESTAMP | NOT NULL | auto | ์์ ์ผ |
| ์ปฌ๋ผ | ํ์ | ์ ์ฝ์กฐ๊ฑด | ์ค๋ช |
|---|---|---|---|
| id | UUID | PK | ๊ณ ์ ์๋ณ์ |
| user_id | UUID | FK โ users.id, NOT NULL | ์์ ์ฌ์ฉ์ |
| event_id | UUID | FK โ events.id | ๊ด๋ จ ์ด๋ฒคํธ (nullable) |
| type | ENUM | NOT NULL | ALERT, WARNING, INFO, SUCCESS |
| title | VARCHAR(200) | NOT NULL | ์ ๋ชฉ |
| message | TEXT | NOT NULL | ๋ฉ์์ง |
| created_at | TIMESTAMP | NOT NULL | ์์ฑ์ผ |
| ํ ์ด๋ธ | ์ธ๋ฑ์ค๋ช | ์ปฌ๋ผ |
|---|---|---|
| users | idx_users_email | |
| users | idx_users_approved | approved |
| cameras | idx_cameras_connected | connected |
| cameras | idx_cameras_enabled | enabled |
| cameras | idx_cameras_analysis_enabled | analysis_enabled |
| events | idx_events_camera_id | camera_id |
| events | idx_events_risk | risk |
| events | idx_events_type | type |
| events | idx_events_status | status |
| events | idx_events_occurred_at | occurred_at |
| event_actions | idx_event_actions_event_id | event_id |
| notifications | idx_notifications_user_id | user_id |
| notifications | idx_notifications_created_at | created_at |
- Access Token: 15๋ถ, Authorization ํค๋
- Refresh Token: 7์ผ, HttpOnly Cookie, Redis ์ ์ฅ
JwtAuthenticationFilter: JWT ๊ฒ์ฆ ๋ฐ SecurityContext ์ค์ - ์ธ์ฆ ์์ธ ๊ฒฝ๋ก:
/api/auth/login,/api/auth/signup,/api/auth/refresh,/internal/**
USER: ๊ธฐ๋ณธ ์ฌ์ฉ์ (ํ ๋น๋ ์นด๋ฉ๋ผ๋ง ์ ๊ทผ)ADMIN: ๋ชจ๋ ์นด๋ฉ๋ผ ์ ๊ทผ (์นด๋ฉ๋ผ ๊ถํ ์์ ๋ถ๊ฐ), ์ฌ์ฉ์ ๊ด๋ฆฌ
- ์ฌ์ฉ์๋ณ SSE ์ฐ๊ฒฐ ๊ด๋ฆฌ
- ํ์์์: 30๋ถ
| ์ด๋ฒคํธ | ์ค๋ช |
|---|---|
connect |
์ฐ๊ฒฐ ์ฑ๊ณต |
notification |
์ ์๋ฆผ |
camera |
์นด๋ฉ๋ผ ์ํ ๋ณ๊ฒฝ |
event |
์ด๋ฒคํธ ์์ฑ/์์ |
event-deleted |
์ด๋ฒคํธ ์ญ์ |
member |
๋ฉค๋ฒ ๋ณ๊ฒฝ |
action-pending |
์ก์ ์น์ธ ๋๊ธฐ (Human-in-the-Loop) |
action-resolved |
์ก์ ํด๊ฒฐ๋จ |
| ์ฝ๋ | HTTP | ๋ฉ์์ง |
|---|---|---|
| EMAIL_NOT_FOUND | 401 | ๋ฑ๋ก๋์ง ์์ ์ด๋ฉ์ผ์ ๋๋ค. |
| INVALID_PASSWORD | 401 | ๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค. |
| USER_NOT_APPROVED | 403 | ๊ด๋ฆฌ์ ์น์ธ ๋๊ธฐ ์ค์ ๋๋ค. |
| DUPLICATE_EMAIL | 400 | ์ด๋ฏธ ๋ฑ๋ก๋ ์ด๋ฉ์ผ์ ๋๋ค. |
| REFRESH_TOKEN_NOT_FOUND | 401 | Refresh token์ด ์์ต๋๋ค. |
| INVALID_REFRESH_TOKEN | 401 | ์ ํจํ์ง ์์ refresh token์ ๋๋ค. |
| INVALID_USER | 401 | ์ ํจํ์ง ์์ ์ฌ์ฉ์์ ๋๋ค. |
| AUTHENTICATION_REQUIRED | 401 | ์ธ์ฆ์ด ํ์ํฉ๋๋ค. |
| USER_NOT_FOUND | 401 | ์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. |
| CURRENT_PASSWORD_MISMATCH | 400 | ํ์ฌ ๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค. |
| PASSWORD_TOO_SHORT | 400 | ์ ๋น๋ฐ๋ฒํธ๋ 6์ ์ด์์ด์ด์ผ ํฉ๋๋ค. |
| USER_DELETED | 403 | ํํดํ ๊ณ์ ์ ๋๋ค. |
| ์ฝ๋ | HTTP | ๋ฉ์์ง |
|---|---|---|
| USER_ID_REQUIRED | 400 | ์ฌ์ฉ์ ID๊ฐ ํ์ํฉ๋๋ค. |
| USER_NOT_FOUND_BY_ID | 404 | ์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. |
| ์ฝ๋ | HTTP | ๋ฉ์์ง |
|---|---|---|
| CAMERA_NOT_FOUND | 404 | ์นด๋ฉ๋ผ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. |
| CAMERA_ACCESS_DENIED | 403 | ํด๋น ์นด๋ฉ๋ผ์ ๋ํ ์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค. |
| CAMERA_NOT_CONNECTED | 400 | ์นด๋ฉ๋ผ๊ฐ ์ฐ๊ฒฐ๋์ด ์์ง ์์ต๋๋ค. |
| ์ฝ๋ | HTTP | ๋ฉ์์ง |
|---|---|---|
| EVENT_NOT_FOUND | 404 | ์ด๋ฒคํธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. |
| ์ฝ๋ | HTTP | ๋ฉ์์ง |
|---|---|---|
| S3_UPLOAD_FAILED | 500 | S3 ์ ๋ก๋์ ์คํจํ์ต๋๋ค. |
| S3_DOWNLOAD_FAILED | 500 | S3 ๋ค์ด๋ก๋์ ์คํจํ์ต๋๋ค. |
| S3_DELETE_FAILED | 500 | S3 ์ญ์ ์ ์คํจํ์ต๋๋ค. |
| CLIP_EXTRACTION_FAILED | 500 | ํด๋ฆฝ ์ถ์ถ์ ์คํจํ์ต๋๋ค. |
| ์ฝ๋ | HTTP | ๋ฉ์์ง |
|---|---|---|
| FORBIDDEN | 403 | ๊ถํ์ด ์์ต๋๋ค. |
| INTERNAL_SERVER_ERROR | 500 | ์๋ฒ ๋ด๋ถ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. |
- ์นด๋ฉ๋ผ ๋๊ธฐํ:
/internal/mediamtx/syncํธ์ถ ์ MediaMTX API์์ ์คํธ๋ฆผ ๋ชฉ๋ก ์กฐํ ํ DB ๋๊ธฐํ - ์ธ์ฆ ์์: MediaMTX โ
/internal/mediamtx/authํธ์ถ- WebRTC read: JWT ๊ฒ์ฆ
- SRT publish: ID/PW ๊ฒ์ฆ
- RTSP/HLS read: ์ธ์ฆ ์์ (๋ด๋ถ๋ง)
- ํด๋ฆฝ ์ ์ฅ/์กฐํ/์ญ์
- ๋ฒํท:
aegis(๊ธฐ๋ณธ๊ฐ, ํ๊ฒฝ๋ณ์AWS_S3_BUCKET์ผ๋ก ๋ณ๊ฒฝ ๊ฐ๋ฅ) - ํค ํ์:
clips/{eventId}.mp4
ํด๋ฆฝ ์ ์ฅ ๊ตฌ์กฐ:
aegis/
โโโ clips/ # ์ด๋ฒคํธ ํด๋ฆฝ (Agent๊ฐ presigned URL๋ก ์ง์ ์
๋ก๋)
โโโ {event_id}.mp4
์ฐธ๊ณ : temp/clips/ ๊ฒฝ๋ก์ ๊ด๋ จ ๋ฉ์๋(tempClipExists, moveClipFromTemp)๋ ํ์ฌ ์ฌ์ฉ๋์ง ์์ต๋๋ค (Known Issues ์ฐธ์กฐ).
| ํค | ํ์ | ๋ฐธ๋ฅ | TTL | ์ค๋ช |
|---|---|---|---|---|
refresh_token:{token} |
String | userId (UUID) |
7์ผ | Refresh Token โ ์ฌ์ฉ์ ๋งคํ |
mediamtx:sync:lock |
String | "locked" |
1์ด | MediaMTX ๋๊ธฐํ ์ค๋ณต ๋ฐฉ์ง ์ ๊ธ |
analysis:cameras |
String | [{"id":"uuid","name":"cam1","location":"1์ธต ๋ก๋น"},...] |
์์ | AI ๋ถ์ ๋์ ์นด๋ฉ๋ผ ๋ชฉ๋ก (JSON ๋ฐฐ์ด) |
Pub/Sub ์ฑ๋:
| ์ฑ๋ | ๋ฉ์์ง | ์ค๋ช |
|---|---|---|
camera:analysis:update |
"sync" |
๋ถ์ ์นด๋ฉ๋ผ ๋ชฉ๋ก ๋ณ๊ฒฝ ์๋ฆผ (Python Agent ๊ตฌ๋ ) |
./gradlew bootJar
java -jar build/libs/aegis-backend-1.0.0.jarCaddy ๋ฆฌ๋ฒ์ค ํ๋ก์๋ฅผ ํตํด /api/* ๊ฒฝ๋ก๋ก ์๋น์ค๋ฉ๋๋ค.
- ๋ด๋ถ ํฌํธ: 8080
- ์ธ๋ถ ์ ๊ทผ:
https://localhost/api/*
์ต์ข ๊ฐ์ฌ์ผ: 2026-02-12
| ํ์ผ | ๋ฌธ์ | ์์ธ |
|---|---|---|
S3Service.java |
tempClipExists() ๋ฏธ์ฌ์ฉ |
temp/clips ๊ฒฝ๋ก ํ์ธ ๋ฉ์๋, ํธ์ถ์ฒ ์์ |
S3Service.java |
moveClipFromTemp() ๋ฏธ์ฌ์ฉ |
temp โ clips ์ด๋ ๋ฉ์๋, ํธ์ถ์ฒ ์์ |
| ํ์ผ | ๊ธฐ๋ฅ | ํ์ฌ ์ํ |
|---|---|---|
Event.report |
์์ธ ๋ณด๊ณ ์ | ํ๋๋ง ์กด์ฌ, AI Agent์์ ์์ฑํ์ง ์์ |
| ํ์ผ | ๋ฌธ์ | ์ฌ๊ฐ๋ | ๊ถ์ฅ ์กฐ์น |
|---|---|---|---|
application.properties |
JWT Secret ๊ธฐ๋ณธ๊ฐ ์ฌ์ฉ | ๐ด ๋์ | ํ๊ฒฝ ๋ณ์๋ก ์ฃผ์ , 256bit ์ด์ ๋๋ค๊ฐ ์ฌ์ฉ |
DataInitializer.java |
Admin ๋น๋ฐ๋ฒํธ ๊ธฐ๋ณธ๊ฐ | ๐ก ์ค๊ฐ | ํ๊ฒฝ ๋ณ์๋ก ์ฃผ์ ๋๋ ์ฒซ ๋ก๊ทธ์ธ ์ ๋ณ๊ฒฝ ๊ฐ์ |
/internal/** ๊ฒฝ๋ก |
์ธ์ฆ ์์ (๋ด๋ถ๋ง ๊ฐ์ ) | ๐ก ์ค๊ฐ | ์ด์ํ๊ฒฝ์์ IP ํ์ดํธ๋ฆฌ์คํธ ์ ์ฉ |
| ํญ๋ชฉ | ์ค๋ช |
|---|---|
| SSE ํ์์์ | SseEmitterService 30๋ถ ํ์์์, ์ฌ์ฐ๊ฒฐ ํ์ |
| ์นด๋ฉ๋ผ ์ญ์ ๋ฏธ์ง์ | MediaMTX ์คํธ๋ฆผ ์ข ๋ฃ ์ connected=false๋ง ์ฒ๋ฆฌ, ์ญ์ API ์์ |