-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat]#19 트래킹 GPS #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8172c72
57c0d97
c0509d0
41ca956
7c7d219
2f198bd
a1cc3c2
5199719
7f3e238
8397428
594b6c0
f34a536
f544939
7ab3ca3
a189be8
8f6a244
349e814
445e75d
f506a72
0b145de
e5beefb
569514d
ace41a6
ff6beb3
324c6ea
457fafb
0cb828a
c274afe
cb43205
5819cdb
2598403
1fb1de7
cbbda93
d27b7b8
73bc7e0
c0d6d99
8d76665
4645a2f
99bf9c9
a88e749
2408eb0
9a7bb76
e3897f6
ee96567
7054f1e
0b2b4cc
ffa4cfc
c227637
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # AGENTS.md | ||
|
|
||
| ## Communication | ||
| - Always reply in Korean unless the user explicitly asks for another language. | ||
| - Keep answers direct and practical. Avoid repeating the same explanation after the user has already acknowledged it. | ||
| - When the user asks "확인해줘", "봐줘", "어떻게 돼", "뭐가 문제야", or similar, treat it as analysis-only unless they explicitly ask for code changes. | ||
| - Do not edit files unless the user clearly says something like "수정해줘", "고쳐줘", "반영해줘", "만들어줘", "추가해줘", or "삭제해줘". | ||
| - If a fix is likely, explain the cause, affected files, and proposed patch first. Wait for explicit approval before applying it. | ||
| - If you accidentally changed code without approval, say exactly which files changed and offer to revert only your own changes. | ||
|
|
||
| ## User Preferences | ||
| - The user wants investigation before implementation. | ||
| - The user dislikes unrequested code edits. | ||
| - The user prefers concrete answers based on the current codebase, not generic guesses. | ||
| - When diagnosing backend/frontend integration issues, clearly separate: | ||
| - what the backend currently expects | ||
| - what the frontend must send | ||
| - what environment/config values must match | ||
| - what is only an assumption | ||
| - If sensitive values such as secrets, tokens, DB passwords, or JWT secrets appear in screenshots or logs, warn briefly and recommend rotation if they may have been shared. | ||
|
|
||
| ## Project | ||
| - This is a Spring Boot backend project. | ||
| - Use Java 21 and Gradle. | ||
| - Follow the existing package structure, naming, and style. | ||
| - Prefer existing services, repositories, DTOs, and response conventions over introducing new abstractions. | ||
|
|
||
| ## Commands | ||
| - Use `rg` first for searching. | ||
| - Run focused tests before broad tests when checking a narrow change. | ||
| - Common commands: | ||
| - `./gradlew test --tests <fully.qualified.TestClass>` | ||
| - `./gradlew test` | ||
| - `./gradlew build` | ||
| - If full tests fail because local infrastructure such as PostgreSQL or Redis is unavailable, report that clearly and distinguish it from failures caused by the code change. | ||
|
|
||
| ## Editing Rules | ||
| - Never revert or overwrite user changes unless explicitly requested. | ||
| - Keep changes narrowly scoped to the requested task. | ||
| - Do not commit secrets, environment values, generated local files, or unrelated formatting churn. | ||
| - Before editing, state what files will be touched and why. | ||
| - After editing, summarize the exact files changed and verification performed. |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| package com.semosan.api.domain.tracking.config; | ||
|
|
||
| import com.semosan.api.common.config.TrackingProperties; | ||
| import com.semosan.api.domain.tracking.service.TrackingStreamConsumer; | ||
| import jakarta.annotation.PreDestroy; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.beans.factory.InitializingBean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.data.redis.connection.RedisConnectionFactory; | ||
| import org.springframework.data.redis.connection.stream.Consumer; | ||
| import org.springframework.data.redis.connection.stream.MapRecord; | ||
| import org.springframework.data.redis.connection.stream.ReadOffset; | ||
| import org.springframework.data.redis.connection.stream.StreamOffset; | ||
| import org.springframework.data.redis.stream.StreamMessageListenerContainer; | ||
|
|
||
| import java.net.InetAddress; | ||
| import java.net.UnknownHostException; | ||
| import java.time.Duration; | ||
| import java.util.UUID; | ||
|
|
||
| /** | ||
| * Redis Stream(tracking:gps) 의 GPS 점을 소비하는 컨테이너를 부트업한다. | ||
| * - Consumer group 자체는 RedisStreamConfig 에서 사전 생성됨. | ||
| * - 본 클래스는 그룹의 이 인스턴스를 위한 consumer 등록 + listener container 시작. | ||
| * - consumer 이름은 호스트명 + UUID prefix 로 인스턴스별 유일성 확보 (다중 인스턴스 대비). | ||
| */ | ||
| @Slf4j | ||
| @Configuration | ||
| @RequiredArgsConstructor | ||
| public class TrackingStreamListenerConfig implements InitializingBean { | ||
|
|
||
| private final RedisConnectionFactory redisConnectionFactory; | ||
| private final TrackingProperties trackingProperties; | ||
| private final TrackingStreamConsumer trackingStreamConsumer; | ||
|
|
||
| private StreamMessageListenerContainer<String, MapRecord<String, String, String>> container; | ||
|
|
||
| @Override | ||
| public void afterPropertiesSet() { | ||
| var options = StreamMessageListenerContainer | ||
| .StreamMessageListenerContainerOptions.builder() | ||
| .pollTimeout(Duration.ofSeconds(1)) | ||
| .build(); | ||
|
|
||
| container = StreamMessageListenerContainer.create(redisConnectionFactory, options); | ||
|
|
||
| container.receiveAutoAck( | ||
| Consumer.from(trackingProperties.getConsumerGroup(), buildConsumerName()), | ||
| StreamOffset.create(trackingProperties.getStreamKey(), ReadOffset.lastConsumed()), | ||
| trackingStreamConsumer | ||
| ); | ||
|
Comment on lines
+48
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Yes. In StreamMessageListenerContainer, using receiveAutoAck (auto-acknowledge) acknowledges messages when they are received, not after your listener code completes successfully. This is explicitly stated as “Every message is acknowledged when received” for receiveAutoAck, whereas the non-auto-ack mode requires acknowledging via StreamOperations.acknowledge after processing completes successfully [1][2][3]. Recommended pattern for at-least-once with consumer groups: 1) Use external acknowledgement: call receive(...) (autoAck=false) and only acknowledge with StreamOperations.acknowledge after your listener processing succeeds [1][4][3]. This keeps failed/unacknowledged deliveries in the consumer group’s Pending Entries List (PEL) [3][5]. 2) Handle PENDING recovery explicitly: start (or periodically run) a “drain PEL” phase for your consumer group (e.g., read pending/previously delivered entries), and only when the PEL is empty switch to reading new messages (ID “>”). Redis documents that re-delivered pending entries remain pending until explicitly acknowledged via XACK, and the typical recovery approach is to first process the pending backlog (using an initial ID such as 0), then proceed to “>” for new messages [6]. In code terms, the container doesn’t provide a built-in “always re-process PENDING until it succeeds before polling for new” switch; you implement the two-phase (PEL-drain then new) flow by choosing the appropriate StreamRead offsets/requests (PEL history first, then “>”), and by acknowledging only after successful processing [1][3][6]. Citations:
Use manual acknowledgement instead of auto-ack to ensure at-least-once delivery for GPS points.
Switch to 🤖 Prompt for AI Agents |
||
| container.start(); | ||
| log.info("Started Redis Stream listener: stream={} group={}", | ||
| trackingProperties.getStreamKey(), | ||
| trackingProperties.getConsumerGroup()); | ||
| } | ||
|
|
||
| @PreDestroy | ||
| public void stop() { | ||
| if (container != null) { | ||
| container.stop(); | ||
| } | ||
| } | ||
|
|
||
| private static String buildConsumerName() { | ||
| String host; | ||
| try { | ||
| host = InetAddress.getLocalHost().getHostName(); | ||
| } catch (UnknownHostException e) { | ||
| host = "unknown"; | ||
| } | ||
| return host + "-" + UUID.randomUUID().toString().substring(0, 8); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restrict WebSocket origins now (don’t ship wildcard + credentials).
Line 95 currently allows any origin for
/ws/**while Line 98 enables credentials. This leaves the handshake policy overly permissive for a privileged channel. Replace*with an explicit allowlist and keep it environment-driven. Also alignWebSocketConfigLine 29 to the same allowlist.🔒 Suggested change
Also applies to: 101-101
🤖 Prompt for AI Agents