개인프로젝트/기능프로그램_오늘뭐입지

20240514_날씨 모듈 통합

일일일코_장민기 2024. 5. 15. 02:14
728x90

 

 

기존의 날씨 api와 미세먼지 api를 출력하는 jsp의 정보를 하나의 jsp에서 볼 수 있도록 만들 것이다.

간단히 include로 끝날 줄 알았던 작업이 비동기처리를 안 해놔서 다 해야만 했다...

 

전체 코드

더보기

통합 controller

package org.weather.main;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MainController {

//    @PostMapping("/wdMain")
//    public String wdMain(String country, String area) {
    @GetMapping("/wdMain")
    public ModelAndView wdMain() {
        ModelAndView mav = new ModelAndView("wdMain");
//            mav.addObject("country", country);
//            mav.addObject("area", area);
            mav.addObject("country", "경기");
            mav.addObject("area", "용인시수지구");
            mav.setViewName("WDMain");
        return mav;
    }

}

 

- 주석된 코드는 이후 msa 통합 시에 변경될 내용이다.

 

미세먼지 controller

package org.weather.dustApi;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.weather.PaticulatemattervoDto;
import org.weather.PlaceDto;
import org.weather.place.PlaceService;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@RequiredArgsConstructor
@Controller
public class DustApiController {

    private final DustApiService dustApiService;
    private final PlaceService placeService;

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

    @ResponseBody
    @PostMapping("/dustRequestAjax")
    public PaticulatemattervoDto dustRequestAjax(String country, String area){
        log.info("dustRequestAjax");
        PlaceDto placeDto = placeService.findByCountryAndArea(country, area);
        return dustApiService.findByStationname(placeDto);
    }


    @PostConstruct
    public void init()  throws IOException, JSONException {

            String country = "%EA%B2%BD%EA%B8%B0";  // 한글이 아스키코드로 변환되어야 함

            //Url 생성
            String stringUrl =
                    "http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getCtprvnRltmMesureDnsty" +
                            "?sidoName=" + country +
                            "&pageNo=" + "1" +
                            "&numOfRows=" + "100" +
                            "&returnType=" + "json" +
                            "&serviceKey=" + apiKey +
                            "&ver=" + "1.0";

            //HTTP Request
            URL url = new URL(stringUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");

            //응답코드 확인
            //log.info("Response Code: {}", connection.getResponseCode());

            //응답 처리
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String response = String.join("/n", bufferedReader.lines().toList());
            bufferedReader.close();
            connection.disconnect();

            //JSON 데이터 출력
            //log.info("Response: {}", response);

            //JSON 데이터에서 필요한 데이터 추출
            JSONObject jsonObject = new JSONObject(response);
            JSONObject items = jsonObject.getJSONObject("response").getJSONObject("body");
            JSONArray itemsArray = items.getJSONArray("items");

            List<PaticulatemattervoDto> paticulateMatterList = new ArrayList<>();
            for (int i = 0; i < itemsArray.length(); i++) {
                JSONObject item = itemsArray.getJSONObject(i);
                PaticulatemattervoDto paticulatemattervoDto = new PaticulatemattervoDto();
                paticulatemattervoDto.setSidoname(item.getString("sidoName"));
                paticulatemattervoDto.setDatatime(item.getString("dataTime"));
                paticulatemattervoDto.setStationname(item.getString("stationName"));
                paticulatemattervoDto.setPm25grade(item.getString("pm25Grade"));
                paticulatemattervoDto.setPm25flag(item.getString("pm25Flag"));
                paticulatemattervoDto.setPm25value(item.getString("pm25Value"));
                paticulatemattervoDto.setPm10grade(item.getString("pm10Grade"));
                paticulatemattervoDto.setPm10flag(item.getString("pm10Flag"));
                paticulatemattervoDto.setPm10value(item.getString("pm10Value"));
                paticulateMatterList.add(paticulatemattervoDto);
            }

            //List에 들어간 데이터 확인
            //log.info("particulateMatterList: {}", paticulateMatterList.toString());

            dustApiService.dustRequest(paticulateMatterList);
            log.info("DB에 "+"경기"+" 지역 미세먼지 데이터 저장 완료");
    }
}

- 기존의 dustMain 메서드가 사라지고 ajax에 사용되는 dustRequestAjax 메서드가 생겼다.

- @PostConstruct를 통해 서버가 실행되면 미세먼지 데이터를 DB에 저장하도록 만들었다. 이 작업을 하지 않으면 프론트단에서 정보를 바로 출력할 수 없어 에러가 난다

 

package org.weather.weatherApi;

import org.springframework.beans.factory.annotation.Value;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.weather.WeatherDataDTO;
import org.weather.WeatherareavoDTO;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

@Slf4j
@Controller
public class WeatherApiController {

    private final WeatherApiService weatherApiService;

    private WeatherApiController(WeatherApiService weatherApiService){
        this.weatherApiService = weatherApiService;
    }

    @Value("${weather.api.key}")
    String weatherApiKey;

    @ResponseBody
    @PostMapping("/weatherRequest")
    public Map<String, Object> weatherRequest(WeatherareavoDTO weatherareavoDTO){

        String area = weatherareavoDTO.getArea();
        Map<String, Object> returnMap = new HashMap<>();
            returnMap.put("area", area);

        //위도와 경도 가져오기
        Map position = weatherApiService.weatherRequest(area);

        try {

        //api 요청을 위한 url 구성
        String stringURL =
            "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst" +
                "?serviceKey=" + weatherApiKey +
                "&numOfRows=10" +
                "&pageNo=1" +
                "&dataType=JSON" +
                "&base_date=" + weatherareavoDTO.getBase_date() +
                "&base_time=" + weatherareavoDTO.getBase_time() +
                "&nx="+ position.get("nx") +
                "&ny="+ position.get("ny");

            //url로 HTTP Request
            URL url = new URL(stringURL);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");

            //응답코드 확인
            //log.info("Response Code: {}", connection.getResponseCode());

            //응답 처리
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String response = String.join("/n", bufferedReader.lines().toList());
            bufferedReader.close();
            connection.disconnect();
            
            //JSON 데이터 출력(원본)
            //log.info("Response: {}", response);
            
            //JSON 데이터에서 필요한 데이터 추출
            JSONObject jsonObject = new JSONObject(response);
            JSONObject items = jsonObject.getJSONObject("response").getJSONObject("body").getJSONObject("items");
            JSONArray itemsArray = items.getJSONArray("item");

            //DTO로 구성된 List 생성
            List<WeatherDataDTO> weatherDataDTOList = new ArrayList<>();
            for (int i = 0; i < itemsArray.length(); i++) {
                JSONObject item = itemsArray.getJSONObject(i);
                String baseTime = item.getString("baseTime");
                String category = item.getString("category");
                String fcstValue = item.getString("fcstValue");

                //DTO 데이터 재구성
                WeatherDataDTO weatherDataDTO = new WeatherDataDTO();
                    weatherDataDTO.setBaseTime(baseTime);
                    weatherDataDTO.setCategory(category);
                    weatherDataDTO.setFcstValue(category, fcstValue);
                weatherDataDTOList.add(weatherDataDTO);
            }

            //가공된 데이터 출력
            //log.info("WeatherDataDTOList: {}", weatherDataDTOList);
            returnMap.put("weatherDataTime", weatherDataDTOList.get(0).getBaseTime().substring(0, 2));
            returnMap.put("weatherDataDTOList", weatherDataDTOList);

        } catch (IOException | JSONException e) {
            throw new RuntimeException(e);
        }

        return returnMap;
    }
}

 

- 마찬가지로 기존의 weatherMain 메서드가 사라지고 ajax에 사용되는 weatherRequest 메서드가 생겼다.

- 또한 ajax를 처리하기 위해 ModelAndView에서 Map타입으로 return 타입이 변경되었다.

 

jsp들

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>

<h1>Weather and Dust Main Page</h1>

<p>
    <jsp:include page="includeData/includeWeatherMain.jsp"/>
</p>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
        crossorigin="anonymous"></script>
</body>
</html>

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <script type="text/javascript" src="<c:url value='/resources/js/WeatherMain.js'/>"></script>
</head>
<body>

<p>
    <H2 id="response_area"></H2>
    <H3 id="response_time"></H3>
    <span id="response_weatherData"></span>
</p>

<p>
    <jsp:include page="includeDustMain.jsp"/>
</p>

<p>
    <form id="weatherRequest" action="<c:url value='/weatherRequest'/>" method="post">
        <input type="hidden" id="base_date" name="base_date">
        <input type="hidden" id="base_time" name="base_time">
        <input type="hidden" id="country" name="country" value="<c:out value="${country}"/>">
        <input type="hidden" id="user_area" value="<c:out value="${area}"/>">
        지역(경기도 한정):
        <select id="area" name="area">
            <option value="수원시장안구">수원시장안구</option>
            <option value="수원시권선구">수원시권선구</option>
            <option value="수원시팔달구">수원시팔달구</option>
            <option value="수원시영통구">수원시영통구</option>
            <option value="성남시수정구">성남시수정구</option>
            <option value="성남시중원구">성남시중원구</option>
            <option value="성남시분당구">성남시분당구</option>
            <option value="의정부시">의정부시</option>
            <option value="안양시만안구">안양시만안구</option>
            <option value="안양시동안구">안양시동안구</option>
            <option value="부천시원미구">부천시원미구</option>
            <option value="부천시소사구">부천시소사구</option>
            <option value="부천시오정구">부천시오정구</option>
            <option value="광명시">광명시</option>
            <option value="평택시">평택시</option>
            <option value="동두천시">동두천시</option>
            <option value="안산시상록구">안산시상록구</option>
            <option value="안산시단원구">안산시단원구</option>
            <option value="고양시덕양구">고양시덕양구</option>
            <option value="고양시일산동구">고양시일산동구</option>
            <option value="고양시일산서구">고양시일산서구</option>
            <option value="과천시">과천시</option>
            <option value="구리시">구리시</option>
            <option value="남양주시">남양주시</option>
            <option value="오산시">오산시</option>
            <option value="시흥시">시흥시</option>
            <option value="군포시">군포시</option>
            <option value="의왕시">의왕시</option>
            <option value="하남시">하남시</option>
            <option value="용인시처인구">용인시처인구</option>
            <option value="용인시기흥구">용인시기흥구</option>
            <option value="용인시수지구">용인시수지구</option>
            <option value="파주시">파주시</option>
            <option value="이천시">이천시</option>
            <option value="안성시">안성시</option>
            <option value="김포시">김포시</option>
            <option value="화성시">화성시</option>
            <option value="광주시">광주시</option>
            <option value="양주시">양주시</option>
            <option value="포천시">포천시</option>
            <option value="여주시">여주시</option>
            <option value="연천군">연천군</option>
            <option value="가평군">가평군</option>
            <option value="양평군">양평군</option>
        </select>
        <input id="weatherRequestButton" type="submit" value="확인">
    </form>
</p>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
        crossorigin="anonymous"></script>
</body>
</html>

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>

<p>
    <span id="response_dustData"></span>
</p>
* 점검 등으로 인해 미세먼지 상태나 수치가 제대로 표기되지 않을 수 있습니다.

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
        crossorigin="anonymous"></script>
</body>
</html>

 

- 코드를 어느정도 제거하고 싶었지만, 이상하게 코드 제거하면 다 에러가 나온다. 아무튼 개선할 부분

 

js코드

$(function(){

    changeAreaDropdown();
    timeDataSetting();
    weatherDataRequest();
    dustDataRequest();

    $("#weatherRequest").on("submit", function(event){
        event.preventDefault();
        weatherDataRequest();
        dustDataRequest();
    })

}) // $(function()

function timeDataSetting(){
    var base_date = $("#base_date");
    var base_time = $("#base_time");

    /** 현재 시간 및 날짜 설정 */
        // 오늘 날짜 및 시간 설정
    const today = new Date();
    const year = today.getFullYear();
    const month = (today.getMonth()+1).toString().padStart(2, "0");
    const day = today.getDate().toString().padStart(2, "0");
    const formattedDate = year+month+day;
    const hour = today.getHours()
    const now = today.toLocaleTimeString();

    // 오늘 날짜 및 시간 출력
    // console.log("오늘 날짜: " + formattedDate);
    // console.log("현재 시간: " + now);

    /** 최신 날짜 및 시간 지정 */
    let dateForAPI = formattedDate;
    let timeForAPI;

    switch (hour){
        case 0:
        case 1:
            const yesterday = new Date(today);      //today의 복사본 생성
            yesterday.setDate(yesterday.getDate()-1);
            const yesterday_year = yesterday.getFullYear();
            const yesterday_month = (yesterday.getMonth()+1).toString().padStart(2, "0");
            const yesterday_day = yesterday.getDate().toString().padStart(2, "0");
            dateForAPI = yesterday_year+yesterday_month+yesterday_day;
            timeForAPI = "2300";
            break;
        case 2:
        case 3:
        case 4:
            timeForAPI = "0200";
            break;
        case 5:
        case 6:
        case 7:
            timeForAPI = "0500";
            break;
        case 8:
        case 9:
        case 10:
            timeForAPI = "0800";
            break;
        case 11:
        case 12:
        case 13:
            timeForAPI = "1100";
            break;
        case 14:
        case 15:
        case 16:
            timeForAPI = "1400";
            break;
        case 17:
        case 18:
        case 19:
            timeForAPI = "1700";
            break;
        case 20:
        case 21:
        case 22:
            timeForAPI = "2000";
            break;
        default:
            timeForAPI = "2300";
    }

    // 최신 날짜와 시간 출력
    // console.log("최신 날짜: " + dateForAPI);
    // console.log("최신 시간: " + timeForAPI);

    // 최신 날짜와 시간 전송
    base_date.val(dateForAPI);
    base_time.val(timeForAPI);
}
function weatherDataRequest(){
    const base_date = $("#base_date").val();
    const base_time = $("#base_time").val();
    const country = $("#country").val();
    const area = $("#area").val();

    const response_area = $("#response_area");
    const response_time = $("#response_time");
    const response_weatherData = $("#response_weatherData");

    $.ajax({
        type: "post",
        url: "/weatherRequest",
        data: {
            base_date: base_date,
            base_time: base_time,
            country: country,
            area: area
        },
        datatype: "json",
        success: function(response){
            const weatherData = {
                area: response.area,
                time: response.weatherDataTime,
                weatherDataDTOList: response.weatherDataDTOList
            }
            response_area.text(weatherData.area);
            response_time.text("날씨 측정 시간: " + weatherData.time + "시");

            let table = "<table><thead><tr><th>항목</th><th>값</th></tr></thead><tbody>";
            weatherData.weatherDataDTOList.forEach(item => {
                table += "<tr>";
                table += "<td>" + item.category + "</td>";
                table += "<td>" + item.fcstValue + "</td>";
                table += "</tr>";
            });
            table += "</tbody></table>";

            response_weatherData.html(table);
        },
        error: function(){
            console.log("날씨 데이터 ajax 에러")
        }
    })
}
function dustDataRequest(){
    const country = $("#country").val();
    const area = $("#area").val();
    const response_dustData = $("#response_dustData")

    $.ajax({
        type: "post",
        url: "/dustRequestAjax",
        data: {
            country: country,
            area: area
        },
        datatype: "json",
        success: function(response){

            let table = "<table><thead><tr><th>측정 시간</th><th>초미세먼지 상태</th><th>초미세먼지 수치</th><th>미세먼지 상태</th><th>미세먼지 수치</th></tr></thead><tbody>";
            table += "<tr>";
            table +=    "<td>" + response.datatime.substring(11, 16) + "</td>";
            table +=    "<td>" + response.pm25grade + "</td>";
            table +=    "<td>" + response.pm25value + "</td>";
            table +=    "<td>" + response.pm10grade + "</td>";
            table +=    "<td>" + response.pm10value + "</td>";
            table += "</tr>";
            table += "</tbody></table>";

            response_dustData.html(table);
        },
        error: function(){
            console.log("미세먼지 데이터 ajax 에러")
        }
    })

}
function changeAreaDropdown(){
    const user_area = $("#user_area").val();
    $("#area").val(user_area);
}

 

 

오늘은 별 특이하거나 어려운 코드는 없었으니 js 코드만 보겠다

 

$(function(){

    changeAreaDropdown();
    timeDataSetting();
    weatherDataRequest();
    dustDataRequest();

    $("#weatherRequest").on("submit", function(event){
        event.preventDefault();
        weatherDataRequest();
        dustDataRequest();
    })

}) // $(function()

 

- changeAreaDropdown(): 페이지를 불러오면 유저 DB에 저장된 유저 개인의 country와 area가 불러오고 드롭다운에 적용시키는 메서드

- timeDataSetting(): 기존의 현재 시간에 따라 사용할 시간 지정 메서드

- weatherDataRequest(): country와 area 등 PaticulatemattervoDto에 사용되는 정보를 보내서 날씨 측정 데이터를 비동기처리로 불러오는 메서드

- dustDataRequest(): country와 area를 보내면 DB의 측정소를 찾고, 다시 측정소 데이터를 사용해서 미세먼지 측정 데이터를 비동기 처리로 불러오는 메서드

 

출력 화면

 

 

개선점

1. 현재 FK를 달아놓지 않은 덕분에 미세먼지DB와 place DB의 데이터가 불일치하여 일부 날씨 모듈 데이터 출력 시 에러가 발생되고 있다. 아...

2. include에 사용되는 jsp 코드 정보 정리