feat(notification-service): add Dockerfile, actuator health and alert deduplication
Add multi-stage Dockerfile with eclipse-temurin:17 and HEALTHCHECK on /actuator/health. Expose /actuator/health endpoint via spring-boot-starter-actuator. Deduplicate alerts: only notify when a room's CO2 level changes, reset on recovery. Assisted-by: Claude:claude-sonnet-4-6
This commit is contained in:
20
notification_service/Dockerfile
Normal file
20
notification_service/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
# ── Stage 1 : build ──────────────────────────────────────────────────────────
|
||||
FROM eclipse-temurin:17-jdk-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
COPY pom.xml .
|
||||
COPY src ./src
|
||||
|
||||
RUN apk add --no-cache maven && mvn package -DskipTests -q
|
||||
|
||||
# ── Stage 2 : run ─────────────────────────────────────────────────────────────
|
||||
FROM eclipse-temurin:17-jre-alpine
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/target/notification-service-*.jar app.jar
|
||||
|
||||
EXPOSE 8080
|
||||
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
||||
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
@@ -37,6 +37,10 @@
|
||||
<artifactId>spring-dotenv</artifactId>
|
||||
<version>4.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -12,6 +12,8 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
public class AirQualityScheduler {
|
||||
@@ -28,6 +30,9 @@ public class AirQualityScheduler {
|
||||
private final TelegramNotificationService telegramService;
|
||||
private final NotificationProperties props;
|
||||
|
||||
// last alerted level per room — null means no alert is active for that room
|
||||
private final Map<String, Co2Level> lastAlertedLevel = new ConcurrentHashMap<>();
|
||||
|
||||
public AirQualityScheduler(AirQualityService airQualityService,
|
||||
TelegramNotificationService telegramService,
|
||||
NotificationProperties props) {
|
||||
@@ -53,9 +58,19 @@ public class AirQualityScheduler {
|
||||
|
||||
for (SensorReading reading : readings) {
|
||||
Co2Level level = airQualityService.resolveLevel(reading.co2());
|
||||
|
||||
if (airQualityService.isAlertable(level)) {
|
||||
log.info("Alertable reading: room={} co2={} level={}", reading.roomId(), reading.co2(), level);
|
||||
Co2Level previous = lastAlertedLevel.get(reading.roomId());
|
||||
if (level != previous) {
|
||||
log.info("Alert level changed: room={} {} -> {}", reading.roomId(), previous, level);
|
||||
telegramService.sendAlert(reading, level);
|
||||
lastAlertedLevel.put(reading.roomId(), level);
|
||||
}
|
||||
} else {
|
||||
// room recovered — reset so next alert triggers a new notification
|
||||
if (lastAlertedLevel.remove(reading.roomId()) != null) {
|
||||
log.info("Room {} recovered to {}", reading.roomId(), level);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,15 @@ spring:
|
||||
application:
|
||||
name: notification-service
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health
|
||||
endpoint:
|
||||
health:
|
||||
show-details: never
|
||||
|
||||
notification:
|
||||
telegram:
|
||||
bot-token: ${TELEGRAM_BOT_TOKEN}
|
||||
|
||||
Reference in New Issue
Block a user