Skip to content

Commit

Permalink
Merge pull request #37 from CSID-DGU/feat/#29-energyUsage
Browse files Browse the repository at this point in the history
#29 BE : [FEAT] 전력량 데이터 API 구현
  • Loading branch information
bbabbi authored Nov 9, 2024
2 parents b7bef0c + 3ccf59c commit 47de7c9
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 0 deletions.
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));
}
}

0 comments on commit 47de7c9

Please sign in to comment.