목차

프로젝트 생성

기본 설정

application.yml 설정

기상청 초단기예보 OpenAPI 연동 구현

DTO 생성

Service 구현

기상청 초단기예보 OpenAPI 응답 값 파싱

컨트롤러, 프론트 페이지 추가

컨트롤러 생성

메인 페이지 생성

기상청 OpenAPI 가이드 링크

프로젝트 구조

GitHub 링크

실행화면

프로젝트 생성

STS 상단 메뉴 > File > New > Spring Starter Project 선택합니다.

참고사진_1

New Spring Starter Project

Name, Java Version, Group, Artifact, Description, Package 항목을 입력한 후 아래 "Next" 버튼을 누릅니다.

참고사진_2

New Spring Starter Dependencies

검색란에 의존성 키워드를 입력하여 의존성을 추가합니다. (녹색 영역 참조)

필수 의존성 [Lombok, Mustache, Spring Web] 추가 후 아래 "Finish" 버튼을 누릅니다.

참고사진_3

기본 설정

application.yml 설정

application.properties파일의 확장자를 application.yml 확장자로 변경합니다.

apiUrl: http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtFcst

apiKey 변수에 일반 인증키 (Decoding) 값을 넣어줍니다. (아래 그림 참고)

참고사진_4

application.yml
spring:
  application:
    name: weather

#mustache UTF-8
server:
  servlet:
    encoding:
      force: true

---
#mustache 설정
spring:
  mustache:
    cache: false
    servlet:
      expose-request-attributes: true #mustache에서 csrf 토큰 변수 오류 발생 때문에 추가
      expose-session-attributes: true #세션

#기상청_단기예보 ((구)_동네예보) 조회서비스 / 초단기예보조회 / API인증키
apiUrl: http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtFcst
apiKey: [일반 인증키 (Decoding)]

기상청 초단기예보 OpenAPI 연동 구현

DTO 생성

dto 패키지를 생성 후 WeatherDto 클래스 파일을 생성합니다.

패키지 위치 : com.example.weather.dto

WeatherDto.java
package com.example.weather.dto;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class WeatherDto {

    String nx;
    String ny;
    String baseDate;
    String baseTime;

}

ResultDto 클래스 파일을 생성합니다.

패키지 위치 : com.example.weather.dto

ResultDto.java
package com.example.weather.dto;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ResultDto {

    private String resultCode;
    private String message;
    private Object resultData;
    
    @Builder
    public ResultDto (String resultCode, String message, Object resultData, String url) {
        this.resultCode = resultCode;
        this.message    = message;
        this.resultData = resultData;
    }
    
}

Service 구현

service 패키지를 생성 후 WeatherService 클래스 파일을 생성합니다.

패키지 위치 : com.example.weather.service

WeatherService.java
package com.example.weather.service;

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

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import com.example.weather.dto.WeatherDto;

@Service
public class WeatherService {

    @Value("${apiUrl}")
    private String apiUrl;
    
    @Value("${apiKey}")
    private String apiKey;

    /**
     * 초단기예보조회
     * @param weatherDto
     * @return
     * @throws IOException
     */
    public String getWeather(WeatherDto weatherDto) throws IOException {
        
        UriComponents uriBuilder = UriComponentsBuilder
                .fromHttpUrl(apiUrl)                                // api url
                .queryParam("serviceKey", apiKey)                   // 인증키
                .queryParam("dataType", "JSON")                     // 응답자료형식 (XML or JSON)
                .queryParam("numOfRows", 60)                        // 한 페이지 결과 수
                .queryParam("pageNo", 1)                            // 페이지 번호
                .queryParam("base_date", weatherDto.getBaseDate())  // 발표일자
                .queryParam("base_time", weatherDto.getBaseTime())  // 발표시간 (매시간 30분, API 제공은 매시간 45분 이후)
                .queryParam("nx", weatherDto.getNx())               // 예보지점 X 좌표
                .queryParam("ny", weatherDto.getNy())               // 예보지점 Y 좌표
                .build();
        
        System.out.println("api 요청 URL : " + uriBuilder.toUriString());

        URL url                 = new URL(uriBuilder.toUriString());
        HttpURLConnection conn  = (HttpURLConnection) url.openConnection();
        
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Content-type", "application/json");
        
        System.out.println("Response code: " + conn.getResponseCode());
        
        BufferedReader rd;
        if (conn.getResponseCode() >= 200 && conn.getResponseCode() <= 300) {
            rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        }
        else {
            rd = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
        }
        
        StringBuilder sb = new StringBuilder();
        
        String line;
        while ((line = rd.readLine()) != null) {
            sb.append(line);
        }
        rd.close();
        conn.disconnect();

        String result = sb.toString();
        
        System.out.println("result : " + result);
        
        return result;
    }
    
}

기상청 초단기예보 OpenAPI 응답 값 파싱

컨트롤러, 프론트 페이지 추가

컨트롤러 생성

controller 패키지를 생성 후 MainController 클래스 파일을 생성합니다.

패키지 위치 : com.example.weather.controller

MainController.java
package com.example.weather.controller;

import java.io.IOException;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.weather.dto.ResultDto;
import com.example.weather.dto.WeatherDto;
import com.example.weather.service.WeatherService;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class MainController {
    
    private final WeatherService weatherService;

    /**
     * 메인 페이지
     * @return
     */
    @GetMapping("/")
    public String index() {
        return "index";
    }
 
    /**
     * 초단기예보조회
     * @param weatherDto
     * @return
     * @throws IOException
     */
    @PostMapping(value = "/getWeather")
    @ResponseBody
    public ResponseEntity getWeather(@RequestBody WeatherDto weatherDto) throws IOException {
        ResultDto result = new ResultDto();

        result = ResultDto.builder()
                .resultCode("SUCCESS")
                .message("조회가 완료되었습니다.")
                .resultData(weatherService.getWeather(weatherDto))
                .url(null)
                .build();
        
        return new ResponseEntity(result, HttpStatus.OK);
    }
    
}

메인 페이지 생성

templates에 index.mustache 파일을 생성합니다.

index.mustache
<!DOCTYPE html>
<html lang="ko"> 
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>메인 페이지</title>
        <style>
            .container {
                width: 800px !important;
            }
            input {
                width: 100px;
            }
            th {
                background-color: #f8f8f8 !important;
                text-align: center !important;
            }
            td {
                text-align: center;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <div class="row">
                <div class="col-md-12 pt-5">
                    <div class="card mb-4">
                        <h5 class="card-header text-center">메인 페이지</h5>
                        <div class="card-body">
                            <div class="col-md-12 text-center">
                                <table width="80%" style="margin: auto; text-align: center;">
                                    <tr>
                                        <td>예보지점 x값(nx)</td>
                                        <td><input type="text" id="nx" name="nx" value="" /></td>
                                        <td>예보지점 y값(ny)</td>
                                        <td><input type="text" id="ny" name="ny" value="" /></td>
                                    </tr>
                                    <tr>
                                        <td>baseDate</td>
                                        <td><input type="text" id="baseDate" name="baseDate" value="" /></td>
                                        <td>baseTime</td>
                                        <td><input type="text" id="baseTime" name="baseTime" value="" /></td>
                                    </tr>
                                </table>
                                <br />
                                <button type="button" class="btn btn-outline-success" id="btn_weather">날씨 예보 조회</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="row" id="result">
                <div class="col-md-12">
                    <table class="table table-bordered" id="resultTable" style="display: none;">
                        <colgroup>
                            <col width="150px">
                            <col width="150px">
                            <col width="150px">
                            <col width="150px">
                            <col width="150px">
                            <col width="150px">
                        </colgroup>
                        <thead>
                            <tr>
                                <th>일자</th>
                                <th>시간</th>
                                <th>날씨</th>
                                <th>강수형태</th>
                                <th>기온</th>
                                <th>습도</th>
                            </tr>
                        </thead>
                        <tbody>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
        <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
        
        <script>
            $(document).ready(function(){
                $("#btn_weather").click(function(){
                    if ($("#nx").val() == "") {
                        alert("nx 값을 입력하세요.");
                        $("#nx").focus();
                        return false;
                    }
                    
                    if ($("#ny").val() == "") {
                        alert("ny 값을 입력하세요.");
                        $("#ny").focus();
                        return false;
                    }
                    
                    if ($("#baseDate").val() == "") {
                        alert("baseDate 값을 입력하세요.");
                        $("#baseDate").focus();
                        return false;
                    }
                    
                    if ($("#baseTime").val() == "") {
                        alert("baseTime 값을 입력하세요.");
                        $("#baseTime").focus();
                        return false;
                    }

                    let data = {
                        nx: $("#nx").val()
                        , ny: $("#ny").val()
                        , baseDate: $("#baseDate").val()
                        , baseTime: $("#baseTime").val()
                    }
                                        
                    $.ajax({
                        url: '/getWeather',
                        type: 'POST',
                        data: JSON.stringify(data),
                        dataType: 'json',
                        contentType: 'application/json',
                        success: function (data) {
                            let jsonObj = JSON.parse(data.resultData);
                            
                            if (jsonObj.response.header.resultCode == "00") {
                                $("#resultTable tbody").html("");
                                $("#resultTable").show();
                                
                                let items = jsonObj.response.body.items.item;
                                if (items.length > 0) {
                                    // 6시간 데이터를 제공
                                    const row = 6;
                                    
                                    // 추출 데이터 (하늘상태, 강수형태, 기온, 습도)
                                    let categoryList = ["SKY", "PTY", "T1H", "REH"];
                                    
                                    // 2차원 배열 생성
                                    let arr = new Array(row);
                                    for (let i = 0; i < arr.length; i++) {
                                        arr[i] = new Array(6);
                                    }
                                    
                                    // 기상청 데이터에서 필요한 데이터 추출
                                    let k = 0;  // categoryList index
                                    let y = 0;  // arr row
                                    let x = 0;  // arr column
                                    for (let i = 0; i < items.length; i++) {
                                        if (categoryList[k] == items[i].category) {
                                            arr[y][x]       = date_format(items[i].fcstDate);
                                            arr[y][x+1]     = time_format(items[i].fcstTime);
                                            arr[y][x+k+2]   = code_value(items[i].category, items[i].fcstValue);
                                            y++;
                                        }

                                        if (y == row) {
                                            k++;
                                            y = 0;
                                            i = 0;
                                        }
                                    }

                                    let headerRow = "";
                                    for (let y = 0; y < arr[0].length; y++) {
                                        headerRow = $("<tr></tr>");
                                        for (let x = 0; x < arr.length; x++) {
                                            headerRow.append("<td>" + arr[y][x] + "</td>");
                                        }
                                        $("#resultTable tbody").append(headerRow);
                                    }
                                }
                            }
                            else {
                                // api 응답 오류
                            }
                        }
                    });
                    
                    // 날짜 포맷
                    date_format = function(str) {
                        return [str.slice(0, 4), ".", str.slice(4, 6), ".", str.slice(6, 8)].join('');
                    }
                    
                    // 시간 포맷
                    time_format = function(str) {
                        return [str.slice(0, 2), ":", str.slice(2, 4)].join('');
                    }
                    
                    // categoryList 코드 값 변환
                    code_value = function(category, code) {
                        let value = code;
                        if (category == "SKY") {
                            if (code == "1") {
                                value = "맑음";
                            }
                            else if (code == "3") {
                                value = "구름많음";
                            }
                            else if (code == "4") {
                                value = "흐림";
                            }
                        }
                        else if (category == "PTY") {
                            if (code == "1") {
                                value = "비";
                            }
                            else if (code == "2") {
                                value = "비/눈";
                            }
                            else if (code == "3") {
                                value = "눈";
                            }
                            else if (code == "5") {
                                value = "빗방울";
                            }
                            else if (code == "6") {
                                value = "빗방울눈날림";
                            }
                            else if (code == "7") {
                                value = "눈날림";
                            }
                            else {
                                value = "-";
                            }
                        }
                        else if (category == "T1H") {
                            value = code + "℃";
                        }
                        else if (category == "REH") {
                            value = code + "%";
                        }
                        
                        return value;
                    }
                }) 
            });
        </script>
    </body>
</html>

기상청 OpenAPI 가이드 다운로드

https://www.data.go.kr/data/15084084/openapi.do

참고 문서에는 API 규격서와 격자_위경도 파일이 있습니다.

참고사진_5

프로젝트 구조

참고사진_6

GitHub 링크

전체 소스 다운로드

실행화면

참고사진_7

©2024, DevDream