프로젝트 생성
기본 설정
application.yml 설정
기상청 초단기예보 OpenAPI 연동 구현
DTO 생성
Service 구현
기상청 초단기예보 OpenAPI 응답 값 파싱
컨트롤러, 프론트 페이지 추가
컨트롤러 생성
메인 페이지 생성
기상청 OpenAPI 가이드 링크
프로젝트 구조
GitHub 링크
실행화면
검색란에 의존성 키워드를 입력하여 의존성을 추가합니다. (녹색 영역 참조)
application.properties파일의 확장자를 application.yml 확장자로 변경합니다.
apiUrl: http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtFcst
apiKey 변수에 일반 인증키 (Decoding) 값을 넣어줍니다. (아래 그림 참고)
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)]
dto 패키지를 생성 후 WeatherDto 클래스 파일을 생성합니다.
패키지 위치 : com.example.weather.dto
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
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 패키지를 생성 후 WeatherService 클래스 파일을 생성합니다.
패키지 위치 : com.example.weather.service
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;
}
}
controller 패키지를 생성 후 MainController 클래스 파일을 생성합니다.
패키지 위치 : com.example.weather.controller
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 파일을 생성합니다.
<!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>
https://www.data.go.kr/data/15084084/openapi.do
참고 문서에는 API 규격서와 격자_위경도 파일이 있습니다.
기상청 api | |
---|---|
2024.08.08 | 기상청 초단기예보 OpenAPI 구현하기 (2) |
2024.08.05 | 기상청 초단기예보 OpenAPI 구현하기 (1) |