Skip to content
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

#29 BE : [FEAT] 전력량 데이터 API 구현 #37

Merged
merged 13 commits into from
Nov 9, 2024
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
6 changes: 6 additions & 0 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
//Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
//Jsoup
implementation 'org.jsoup:jsoup:1.18.1'
// JSON 라이브러리
implementation 'org.json:json:20231013'
// Apache HttpClient 라이브러리
implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1'
}

tasks.named('test') {
Expand Down
2 changes: 2 additions & 0 deletions server/src/main/java/org/cecd/server/ServerApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class ServerApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.cecd.server.controller;

import org.cecd.server.dto.EnergyUsageResponse;
import org.cecd.server.service.EnergyDataService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/energy-usage")
public class EnergyDataController {

private final EnergyDataService energyDataService;

public EnergyDataController(EnergyDataService energyDataService) {
this.energyDataService = energyDataService;
}

@GetMapping
public List<EnergyUsageResponse> getEnergyUsage() {
return energyDataService.getAllEnergyUsages();
}
}
1 change: 1 addition & 0 deletions server/src/main/java/org/cecd/server/domain/Device.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class Device {
@GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "device_id")
private Long id;

private String sensorNumber;
private String deviceImg;
private String deviceName;
private String buildingName; // ex : 신공학관
Expand Down
31 changes: 31 additions & 0 deletions server/src/main/java/org/cecd/server/domain/EnergyData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.cecd.server.domain;


import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.*;

import java.time.LocalDateTime;

@Entity
@Builder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class EnergyData {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String sensorNumber;
private LocalDateTime dateTime;
private String energy;
private String power;
private String current;
private String voltage;
private boolean enable;
private int threshold;
}

10 changes: 10 additions & 0 deletions server/src/main/java/org/cecd/server/dto/EnergyUsageResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.cecd.server.dto;

import java.time.LocalDateTime;

public record EnergyUsageResponse(
String sensorNumber,
LocalDateTime dateTime,
double result,
double percentage
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import org.cecd.server.domain.Device;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface DeviceRepository extends JpaRepository<Device, Long> {
List<Device> findByBuildingNameAndLocation(String buildingName, String location);
List<Device> findByBuildingName(String buildingName);
Optional<Device> findById(Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.cecd.server.repository;

import org.cecd.server.domain.EnergyData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.Optional;

@Repository
public interface EnergyDataRepository extends JpaRepository<EnergyData, Long> {
Optional<EnergyData> findBySensorNumberAndDateTime(String sensorNumber, LocalDateTime dateTime);
}
207 changes: 207 additions & 0 deletions server/src/main/java/org/cecd/server/service/EnergyDataService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package org.cecd.server.service;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import org.cecd.server.domain.EnergyData;
import org.cecd.server.dto.EnergyUsageResponse;
import org.cecd.server.repository.EnergyDataRepository;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
import java.util.stream.Collectors;

@Service
public class EnergyDataService {

@Autowired
private EnergyDataRepository energyDataRepository;
@Autowired
private EntityManagerFactory entityManagerFactory;
private final int THRESHOLD = 57056;

@Scheduled(fixedRate = 300000) // 5분마다 크롤링
public void crawlData() {
List<String> urls = List.of(
"https://swiu_testbed.dongguk.edu/sensorboard/67/dataview?senMngno=000100010000000067&prjtype=&sengrpcd=0001&prjgrpcd=0001",
"https://swiu_testbed.dongguk.edu/sensorboard/68/dataview?senMngno=000100010000000068&prjtype=&sengrpcd=0001&prjgrpcd=0001"
// 추가 URL
);

for (String url : urls) {
crawlDataFromUrl(url);
}
}

private void crawlDataFromUrl(String url) {
try {
// SSL 인증서 무시 설정
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}

public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};

SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

HostnameVerifier allHostsValid = (hostname, session) -> true;
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);

// 데이터 가져오기
URL siteURL = new URL(url);
HttpsURLConnection connection = (HttpsURLConnection) siteURL.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder content = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();

// Jsoup으로 HTML 파싱
Document doc = Jsoup.parse(content.toString());

// 테이블 찾기
Element table = doc.selectFirst(".board-table");
if (table != null) {
Elements rows = table.select("tr");
for (int i = 1; i < rows.size(); i++) { // 첫 번째 행은 헤더이므로 제외
Element row = rows.get(i);
Elements cells = row.select("td");

if (cells.size() >= 3) {
String sensorId = cells.get(0).text().trim();

// 날짜 정보를 LocalDateTime 형식으로 변환
Element dateSpan = cells.get(1).selectFirst("span");
String dateString = (dateSpan != null && dateSpan.hasAttr("data-date-time")) ?
dateSpan.attr("data-date-time") : "N/A";

// "(Korean Standard Time)" 제거
if (dateString.contains("(")) {
dateString = dateString.substring(0, dateString.indexOf("(")).trim();
}

// 날짜를 LocalDateTime으로 변환
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dd yyyy HH:mm:ss 'GMT'Z", Locale.ENGLISH);
ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateString, formatter);
LocalDateTime dateTime = zonedDateTime.toLocalDateTime();

String data = cells.get(2).text().trim();

// 특정 값이 포함된 데이터 (onoff와 lock) 제외
if (data.contains("\"onoff\":\"1\"") && data.contains("\"lock\":\"0\"")) {
continue;
}

// 중복 데이터가 존재하는지 확인
Optional<EnergyData> existingData = energyDataRepository.findBySensorNumberAndDateTime(sensorId, dateTime);
if (existingData.isPresent()) {
continue; // 중복된 데이터가 이미 존재하면 저장하지 않음
}

// 새로운 데이터 저장
EnergyData energyData = EnergyData.builder()
.sensorNumber(sensorId)
.dateTime(dateTime)
.energy(extractJsonField(data, "energy"))
.power(extractJsonField(data, "power"))
.current(extractJsonField(data, "current"))
.voltage(extractJsonField(data, "voltage"))
.enable(Boolean.parseBoolean(extractJsonField(data, "enable")))
.threshold((int) Double.parseDouble(extractJsonField(data, "threshold")))
.build();

energyDataRepository.save(energyData);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

// 매일 자정 테이블 초기화
@Scheduled(cron = "0 0 0 * * *")
public void resetTableData() {
energyDataRepository.deleteAll();
resetAutoIncrement();
System.out.println("데이터베이스 테이블이 초기화되었습니다.");
}

private void resetAutoIncrement() {
String resetSql = "ALTER TABLE energy_data AUTO_INCREMENT = 1";
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
entityManager.createNativeQuery(resetSql).executeUpdate();
transaction.commit();
} catch (Exception e) {
if (transaction.isActive()) {
transaction.rollback();
}
e.printStackTrace();
} finally {
entityManager.close();
}
}

// JSON 문자열에서 특정 필드를 추출하는 유틸리티 메서드
private String extractJsonField(String json, String fieldName) {
String pattern = String.format("\"%s\":\"([^\"]+)\"", fieldName);
java.util.regex.Pattern regex = java.util.regex.Pattern.compile(pattern);
java.util.regex.Matcher matcher = regex.matcher(json);
return matcher.find() ? matcher.group(1) : "";
}

public EnergyDataService(EnergyDataRepository energyDataRepository) {
this.energyDataRepository = energyDataRepository;
}

public List<EnergyUsageResponse> getAllEnergyUsages() {
return energyDataRepository.findAll().stream()
.map(data -> {
double current = Double.parseDouble(data.getCurrent());
double voltage = Double.parseDouble(data.getVoltage());
double result = current * voltage;
double percentage = (result / THRESHOLD) * 100;

return new EnergyUsageResponse(
data.getSensorNumber(),
data.getDateTime(),
result,
percentage
);
})
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
}
}
Loading