Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 58 additions & 60 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,79 +1,77 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.4'
id 'io.spring.dependency-management' version '1.1.7'
id 'java'
id 'org.springframework.boot' version '3.4.4'
id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.factoreal'
version = '0.0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'

// MySQL Connector
runtimeOnly 'com.mysql:mysql-connector-j'
// H2 Database
// runtimeOnly 'com.h2database:h2'

annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// MQTT 메세지 송수신을 위한 의존성 추가
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'

// swagger doc
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'

// flyway db
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'

// AWS SDK for IoT Data Plane
implementation platform('software.amazon.awssdk:bom:2.25.19')
implementation 'software.amazon.awssdk:secretsmanager'
implementation 'software.amazon.awssdk:regions'
implementation 'software.amazon.awssdk:iotdataplane'
implementation 'software.amazon.awssdk:sns'

// AWS 테스트용 LocalStack
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:localstack'
testImplementation 'software.amazon.awssdk:sns'

// Websocket
implementation("org.springframework.boot:spring-boot-starter-websocket:3.4.5")

// kafka
implementation 'org.springframework.kafka:spring-kafka'
testImplementation 'org.springframework.kafka:spring-kafka-test'

// WebSocket
implementation 'org.springframework.boot:spring-boot-starter-websocket'

// ELK
implementation 'org.elasticsearch.client:elasticsearch-rest-high-level-client:7.17.21'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'

// MySQL Connector
runtimeOnly 'com.mysql:mysql-connector-j'
// H2 Database
// runtimeOnly 'com.h2database:h2'

annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// MQTT 메세지 송수신을 위한 의존성 추가
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'

// swagger doc
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'

// flyway db
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'

// AWS SDK for IoT Data Plane
implementation platform('software.amazon.awssdk:bom:2.25.19')
implementation 'software.amazon.awssdk:secretsmanager'
implementation 'software.amazon.awssdk:regions'
implementation 'software.amazon.awssdk:iotdataplane'
implementation 'software.amazon.awssdk:sns'

// AWS 테스트용 LocalStack
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:localstack'
testImplementation 'software.amazon.awssdk:sns'

// Websocket
implementation("org.springframework.boot:spring-boot-starter-websocket:3.4.5")

// kafka
implementation 'org.springframework.kafka:spring-kafka'
testImplementation 'org.springframework.kafka:spring-kafka-test'

// WebSocket
implementation 'org.springframework.boot:spring-boot-starter-websocket'
}

tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform()
}
33 changes: 0 additions & 33 deletions elk-compose.yml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@

public interface SensorRepository extends JpaRepository<Sensor, String> {
List<Sensor> findByZone(Zone zone);

List<Sensor> findByZone_ZoneId(String zoneId);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.factoreal.backend.messaging.grafana;

import com.factoreal.backend.domain.sensor.entity.Sensor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.UUID;

@Component
public class DashboardFactory {

private static final ObjectMapper MAPPER = new ObjectMapper();

/**
* 센서별 패널을 포함한 대시보드 JSON 반환
*/
public String build(String zoneId,
List<Sensor> sensors,
String datasourceUid) throws JsonProcessingException {

// 루트 JSON 객체
ObjectNode root = MAPPER.createObjectNode();
ObjectNode dash = root.putObject("dashboard");

// 대시보드 메타
dash.put("uid", UUID.randomUUID().toString().substring(0, 12));
dash.put("title", zoneId);
dash.put("timezone", "browser");

// 자동 데이터 갱신: 최근 15분의 시계열을 2초마다 갱신합니다
dash.set("time", MAPPER.readTree("{\"from\":\"now-15m\",\"to\":\"now\"}"));
dash.put("refresh", "2s");

// panels 배열
ArrayNode panels = dash.putArray("panels");

// 센서별 패널 생성
for (int i = 0; i < sensors.size(); i++) {
String sensorId = sensors.get(i).getSensorId();

// 패널 객체
ObjectNode panel = MAPPER.createObjectNode();
panel.put("id", i + 1);
panel.put("type", "timeseries");
panel.put("title", sensorId + " (Last 15min)");
panel.set("gridPos", MAPPER.readTree(
String.format("{\"x\": %d, \"y\": 0, \"w\": 12, \"h\": 8}",
(i % 2) * 12) // 좌우 2열 배치
));
panel.put("datasource", datasourceUid);

// targets 배열
ArrayNode targets = panel.putArray("targets");
ObjectNode tgt = targets.addObject();
tgt.put("refId", "A");
tgt.put("measurement", "ENVIRONMENT");
tgt.put("policy", "default");
tgt.put("resultFormat", "time_series");
tgt.put("orderByTime", "ASC");

// SELECT clause: raw field(val)
ArrayNode select = tgt.putArray("select");
ArrayNode selectClause = select.addArray();

ObjectNode fieldStep = MAPPER.createObjectNode();
fieldStep.put("type", "field");
fieldStep.putArray("params").add("val");
selectClause.add(fieldStep);

// Tag filter: sensor = current sensorId
ArrayNode tags = tgt.putArray("tags");
ObjectNode tagFilter = MAPPER.createObjectNode();
tagFilter.put("key", "sensorId::field");
tagFilter.put("operator", "=");
tagFilter.put("value", sensorId);
tags.add(tagFilter);

tgt.put("datasource", datasourceUid);
tgt.put("hide", false);

panels.add(panel);
}

// 기타 옵션
root.put("folderId", 0);
root.put("overwrite", false);

return MAPPER.writeValueAsString(root);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.factoreal.backend.messaging.grafana;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

@Slf4j
@Component
@RequiredArgsConstructor
public class GrafanaClient {

private final RestTemplateBuilder restTemplateBuilder;

@Value("${grafana.url}")
private String url;

@Value("${grafana.api-key}")
private String apiKey;

private RestTemplate rest() {
return restTemplateBuilder.rootUri(url).build();
}

public String createDashboard(String dashboardJson) {
RestTemplate restTemplate = rest();

// 1) 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(apiKey);
headers.setContentType(MediaType.APPLICATION_JSON);

// 2) HttpEntity에 payload + headers 담기
HttpEntity<String> entity = new HttpEntity<>(dashboardJson, headers);

// 3) POST 요청
ResponseEntity<Map<String, Object>> res = restTemplate.exchange(
"/api/dashboards/db",
HttpMethod.POST,
entity,
new ParameterizedTypeReference<>() {
}
);

Map<String, Object> body = res.getBody();
if (body == null || body.get("uid") == null) {
log.error("Grafana API 대시보드 생성 실패: {}", body);
throw new IllegalArgumentException("Cannot create dashboard");
}

return body.get("uid").toString();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.factoreal.backend.messaging.grafana;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class GrafanaSensorResponseDto {
private final String sensorId;
private final String sensorType;
private final String iframeUrl;
}
Loading