オープンAPI気象局は短期予報クエリーサービス/パケットを使用する(2)


1.getWeather()関数の作成


前回の記事では、DBはエリア情報を照会し、ダイナミックにエリアを変更しました.
これで、領域+日付+時間値を使用して実行すると、実際にAPI要求を実行するgetWeather()関数が作成されます.
function getWeather() {
	var nullCheck = true;
	$('.emptyCheck').each(function (){
		if ('' == $(this).val()){
			alert($(this).attr('title') + "을(를) 확인바람");
			nullCheck = false;
			return false;	// 빈 값에서 멈춤
		}
	});

	if (nullCheck) {
		var time = $('#time').val()+'00';
		if ($('#time').val() < 10){
			time = "0" + time;
		}
		var areacode = "";
		var cityCode = $('#step1 option:selected').val();
		var countyCode = $('#step2 option:selected').val();
		var townCode = $('#step3 option:selected').val();

		if (townCode == '' && countyCode == '') {
			areacode = cityCode;
		}
		else if(townCode == '' && countyCode != '') {
			areacode = countyCode;
		}
		else if(townCode != '') {
			areacode = townCode;
		}

		var date = $('#datepicker').val();
		var data = {"areacode" : areacode, "baseDate" : date, "baseTime" : time};

		$.ajax({
			url: "/board/getWeather.do",
			data: data,
			dataType: "JSON",
			method : "POST",
			success : function(res){
				console.log(res);
				if (res[0].resultCode != null) {
					alert(res[0].resultMsg);
				}
				else {
					var html = "";
					html += "<tbody><tr><th>nx=" + res[0].nx + "</th><th>ny=" + res[0].ny + "</th></tr>";

					$("#resultWeather").empty();
					$.each(res, function(index, item){
						html += "<tr><td>" + item.category + "</td><td>" + item.obsrValue + "</td></tr>";
					});

					html += "</tbody>";
					$("#resultWeather").append(html);
				}
		        },
		        error : function(xhr){
	                alert(xhr.responseText);
		        }
		});
	}
}
Javascriptに擬似関数を追加します.
htmlタグにclass=emptyCheckの要素を設定します.eachを使用してすべての巡回操作を行う場合、入力されていない値がある場合、alertウィンドウを使用してAPI要求は実行されず、要素が空であることが表示されます.

2.コントローラの変更

@Controller
public class WeatherController
{
    @Autowired
    private WeatherService weatherService;

    @GetMapping(value = "/board/weather.do")
    public String openWeatherPage()
    {
        return "/weather/weather";
    }

    @PostMapping(value = "/board/getWeather.do")
    @ResponseBody
    public List<WeatherDTO> getWeatherInfo(@ModelAttribute AreaRequestDTO areaRequestDTO) throws JsonMappingException, JsonProcessingException, UnsupportedEncodingException, URISyntaxException
    {
        AreaRequestDTO coordinate = this.weatherService.getCoordinate(areaRequestDTO.getAreacode());
        areaRequestDTO.setNx(coordinate.getNx());
        areaRequestDTO.setNy(coordinate.getNy());

        List<WeatherDTO> weatherList = this.weatherService.getWeather(areaRequestDTO);
        return weatherList;
    }

    @PostMapping(value = "/board/weatherStep.do")
    @ResponseBody
    public List<AreaRequestDTO> getAreaStep(@RequestParam Map<String, String> params)
    {
        return this.weatherService.getArea(params);
    }
}
パラメータのAreaRequestDTOは、上のgetWeather()関数のdata(JSON形式)に設定されたareacode、baseDate、baseTime値のDTOです.
getCoordinate()メソッドを受信値の領域コード(行政領域コード)を使用して実行すると、その領域コードに一致するny、nyの値が座標変数に格納されます.
nx、ny値を保存してgetWeather()メソッドを実行すると、AreaRequestDTOの変数を使用してAPI要求が実行されます.
最終的に、WeatherListには、API結果値情報を含むWeatherDTO値が含まれる.

3.サービスの変更


既存のサービスのgetArea()メソッドを保持し、次のコードを追加します.
Service
public interface WeatherService
{
    List<WeatherDTO> getWeather(AreaRequestDTO areaRequestDTO) throws UnsupportedEncodingException, URISyntaxException, JsonMappingException, JsonProcessingException;

    List<AreaRequestDTO> getArea(Map<String, String> params);

    AreaRequestDTO getCoordinate(String areacode);
    
    ResponseEntity<WeatherApiResponseDTO> requestWeatherApi(AreaRequestDTO areaRequestDTO) throws UnsupportedEncodingException, URISyntaxException;
}
ServiceImpl
@Service
@Slf4j
public class WeatherServiceImpl implements WeatherService
{
    @Autowired
    private WeatherMapper weatherMapper;

    @Override
    public List<WeatherDTO> getWeather(AreaRequestDTO areaRequestDTO) throws UnsupportedEncodingException, URISyntaxException, JsonMappingException, JsonProcessingException
    {
        List<WeatherDTO> weatherList = this.weatherMapper.selectSameCoordinateWeatherList(areaRequestDTO); // 날짜, 시간, nx, ny가 requestDTO의 값과 일치하는 데이터가 있는지 검색
        if ( weatherList.isEmpty() )
        {
            ResponseEntity<WeatherApiResponseDTO> response = requestWeatherApi(areaRequestDTO); // 데이터가 하나도 없는 경우 새로 생성
            ObjectMapper objectMapper = new ObjectMapper();
            List<WeatherItemDTO> weatherItemList = response.getBody()
                                                           .getResponse()
                                                           .getBody()
                                                           .getItems()
                                                           .getItem();
            for ( WeatherItemDTO item : weatherItemList )
            {
                weatherList.add(objectMapper.readValue(objectMapper.writeValueAsString(item), WeatherDTO.class));
            }
            this.weatherMapper.insertWeatherList(weatherList); // API요청 후 결과값을 DB에 저장

            return weatherList; 	// 로그를 찍지 않으려면 삭제해도 OK
        }
        return weatherList;	// DB에 기존 저장되어있는 값에서 가져온 List
    }

    @Override
    public List<AreaRequestDTO> getArea(Map<String, String> params)
    {
        return this.weatherMapper.selectArea(params);
    }

    @Override
    public AreaRequestDTO getCoordinate(String areacode)
    {
        return this.weatherMapper.selectCoordinate(areacode);
    }

    @Override
    public ResponseEntity<WeatherApiResponseDTO> requestWeatherApi(AreaRequestDTO areaRequestDTO) throws UnsupportedEncodingException, URISyntaxException
    {
        String url = "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtNcst";
        String serviceKey = "여기에 Decoding Service Key를 입력";
        String encodedServiceKey = URLEncoder.encode(serviceKey, "UTF-8");

        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(new MediaType("application", "JSON", Charset.forName("UTF-8")));

        StringBuilder builder = new StringBuilder(url);
        builder.append("?" + URLEncoder.encode("serviceKey", "UTF-8") + "=" + encodedServiceKey);
        builder.append("&" + URLEncoder.encode("pageNo", "UTF-8") + "=" + URLEncoder.encode("1", "UTF-8"));
        builder.append("&" + URLEncoder.encode("numOfRows", "UTF-8") + "=" + URLEncoder.encode("1000", "UTF-8"));
        builder.append("&" + URLEncoder.encode("dataType", "UTF-8") + "=" + URLEncoder.encode("JSON", "UTF-8"));
        builder.append("&" + URLEncoder.encode("base_date", "UTF-8") + "=" + URLEncoder.encode(areaRequestDTO.getBaseDate(), "UTF-8"));
        builder.append("&" + URLEncoder.encode("base_time", "UTF-8") + "=" + URLEncoder.encode(areaRequestDTO.getBaseTime(), "UTF-8"));
        builder.append("&" + URLEncoder.encode("nx", "UTF-8") + "=" + URLEncoder.encode(areaRequestDTO.getNx(), "UTF-8"));
        builder.append("&" + URLEncoder.encode("ny", "UTF-8") + "=" + URLEncoder.encode(areaRequestDTO.getNy(), "UTF-8"));
        URI uri = new URI(builder.toString());

        ResponseEntity<WeatherApiResponseDTO> response = restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<String>(headers), WeatherApiResponseDTO.class);
        return response;
    }
}
  • getWeather()メソッドは、要求情報を含むオブジェクトAreaResquestDTOを受信し、APIリクエストの前にまずDBクエリを実行するメソッドは、SameCoordinateWeatherList()を選択する.(座標値が付いたExcelファイルを見ると同じ球、同じ穴があるがnx、ny値は同じ位置が多いため、この場合API要求は不要でDBクエリのみ)
  • が実際にAPI要求を実行する部分はrequestWeatherAPI()メソッドである.
  • 4.Mapperの修正

    @Mapper
    public interface WeatherMapper
    {
        List<AreaRequestDTO> selectArea(Map<String, String> params);
    
        AreaRequestDTO selectCoordinate(String areacode);
    
        List<WeatherDTO> selectSameCoordinateWeatherList(AreaRequestDTO areaRequestDTO);
    
        void insertWeatherList(List<WeatherDTO> weatherList);
    }
    mybatis
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.my.board.mapper.WeatherMapper">
    
    	<select id="selectArea" resultType="AreaRequestDTO" parameterType="Map">
    		<choose>
    			<when test="type == 'city'">
    				SELECT
    					areacode, step1
    				FROM
    					tb_weather_area
    				WHERE
    					step2 = "" AND step3 = ""
    				ORDER BY
    					step1
    			</when>
    
    			<when test="type == 'county'">
    				SELECT
    					areacode, step2
    				FROM
    					tb_weather_area
    				WHERE
    					step1 = #{keyword} AND step3 = "" AND step2 != ""
    				ORDER BY
    					step2
    			</when>
    
    			<when test="type == 'town'">
    				SELECT
    					areacode, step3
    				FROM
    					tb_weather_area
    				WHERE
    					step2 = #{keyword} AND step3 != ""
    				ORDER BY
    					step3
    			</when>
    		</choose>
    	</select>
    
    	<select id="selectCoordinate" parameterType="String" resultType="AreaRequestDTO">
    		SELECT
    			gridX as nx, gridY as ny
    		FROM
    			tb_weather_area
    		WHERE
    			areacode = #{areacode}
    	</select>
    
    	<select id="selectSameCoordinateWeatherList" parameterType="AreaRequestDTO" resultType="WeatherDTO">
    		SELECT DISTINCT
    			baseDate, baseTime, category, nx, ny, obsrValue
    		FROM
    			tw_weather_response
    		WHERE
    			baseDate = #{baseDate} AND
    			baseTime = #{baseTime} AND
    			nx = #{nx} AND
    			ny = #{ny}
    	</select>
    
    	<insert id="insertWeatherList" parameterType="WeatherDTO">
    		INSERT INTO tw_weather_response(
    			baseDate
    			,baseTime
    			,category
    			,nx
    			,ny
    			,obsrValue
    		)
    		VALUES
    			<foreach collection="list" item="item" open="(" separator="),(" close=")">
    				#{item.baseDate}
    				,#{item.baseTime}
    				,#{item.category}
    				,#{item.nx}
    				,#{item.ny}
    				,#{item.obsrValue}
    			</foreach>
    	</insert>
    
    </mapper>

    5.データベース表の作成


    dbmsは、API応答値を格納するテーブルを生成する.
    CREATE TABLE `tw_weather_response` (
    	`baseDate` VARCHAR(50) NOT NULL COMMENT '발표일자' COLLATE 'utf8_general_ci',
    	`baseTime` VARCHAR(50) NOT NULL COMMENT '발표시각' COLLATE 'utf8_general_ci',
    	`category` VARCHAR(50) NOT NULL COMMENT '자료구분코드' COLLATE 'utf8_general_ci',
    	`nx` VARCHAR(50) NOT NULL COMMENT '예보지점X좌표' COLLATE 'utf8_general_ci',
    	`ny` VARCHAR(50) NOT NULL COMMENT '예보지점Y좌표' COLLATE 'utf8_general_ci',
    	`obsrValue` VARCHAR(50) NOT NULL COMMENT '실황 값' COLLATE 'utf8_general_ci'
    )
    COMMENT='날씨 API 호출 응답값 저장'
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB
    ;
    完了すると、次のようなテーブル構成が作成されます.

    APIリクエスト後、これらの値はすべて上のテーブルに格納されます.

    6.DTOの作成


    WeatherApiResponseDTO-APIリクエストを受信した後のレスポンス値オブジェクトのために作成されたDTOは、変数名レスポンスが実際のAPIリクエストの結果と一致する必要があります.
    <200,WeatherApiResponseDTO(response=WeatherResponseDTO(header=WeatherHeaderDTO(resultCode=00, resultMsg=NORMAL_SERVICE), body=WeatherBodyDTO(dataType=JSON, items=WeatherItemsDTO(item=[WeatherItemDTO(baseDate=20220124, baseTime=1700, category=PTY, nx=60, ny=120, obsrValue=0.0), WeatherItemDTO(baseDate=20220124, baseTime=1700, category=REH, nx=60, ny=120, obsrValue=43.0), WeatherItemDTO(baseDate=20220124, baseTime=1700, category=RN1, nx=60, ny=120, obsrValue=0.0), WeatherItemDTO(baseDate=20220124, baseTime=1700, category=T1H, nx=60, ny=120, obsrValue=7.6), WeatherItemDTO(baseDate=20220124, baseTime=1700, category=UUU, nx=60, ny=120, obsrValue=-3.3), WeatherItemDTO(baseDate=20220124, baseTime=1700, category=VEC, nx=60, ny=120, obsrValue=100.0), WeatherItemDTO(baseDate=20220124, baseTime=1700, category=VVV, nx=60, ny=120, obsrValue=0.6), WeatherItemDTO(baseDate=20220124, baseTime=1700, category=WSD, nx=60, ny=120, obsrValue=3.5)], numOfRows=0, pageNo=0, totalCount=0)))),[Content-Language:"ko-KR", Set-Cookie:"JSESSIONID=7DMnHdbsSR49wbVAb64iZwZ1kX4vt7RlwRIHymwNqlZqpr9zpkaA2wfDEREDYPr5.amV1c19kb21haW4vbmV3c2t5Mw==; Path=/1360000/VilageFcstInfoService_2.0; HttpOnly; Domain=apis.data.go.kr", Content-Type:"application/json;charset=UTF-8", Content-Length:"909", Date:"Mon, 24 Jan 2022 08:30:25 GMT", Server:"NIA API Server"]>
    実際のAPI要求後に受信した応答値の形式は、上記の形式である.
    import lombok.Data;
    
    @Data
    public class WeatherApiResponseDTO
    {
        private WeatherResponseDTO response;
    }
    WeatherResponseDTO-タイトルと本文を応答オブジェクトから分離するために、上のWeatherApiResponseDTOで作成されたDTO
    import lombok.Data;
    
    @Data
    public class WeatherResponseDTO
    {
        private WeatherHeaderDTO header;
    
        private WeatherBodyDTO   body;
    }
    WeatherHeaderDTO:DTOのタイトルに対応するDTO
    import lombok.Data;
    
    @Data
    public class WeatherHeaderDTO
    {
        private String resultCode;
    
        private String resultMsg;
    }
    WeatherBodyDTO-上の応答DTOにおいてbodyに適合するDTO
    import lombok.Data;
    
    @Data
    public class WeatherBodyDTO
    {
        private String          dataType;
    
        private WeatherItemsDTO items;
    }
    WeatherItemsDTO-BodyDTOからアイテムコンテンツをインポートするDTO
    import java.util.List;
    
    import lombok.Data;
    
    @Data
    public class WeatherItemsDTO
    {
        private List<WeatherItemDTO> item;
    
        private int                  numOfRows;
    
        private int                  pageNo;
    
        private int                  totalCount;
    }
    WeatherItemDTO:各実際の応答値をItems DTOから取得するためのDTO
    import lombok.Data;
    
    @Data
    public class WeatherItemDTO
    {
        private String baseDate;
    
        private String baseTime;
    
        private String category;
    
        private String nx;
    
        private String ny;
    
        private Double obsrValue;
    }
    WeatherDTO:View側に送信されるDTO
    @Data
    public class WeatherDTO
    {
        // 발표 일자
        private String baseDate;
    
        // 발표 시각
        private String baseTime;
    
        // 자료구분코드
        private String category;
    
        // 예보지점 X좌표
        private String nx;
    
        // 예보지점 Y좌표
        private String ny;
    
        // 실황 값
        private Double obsrValue;
    }
    
    これまでに8つのDTOが作成されています.

    上のWeatherApiResponseDTOからPTYクラスの応答値を知りたいなら
    ResponseEntity<WeatherApiResponseDTO> response = restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<String>(headers), WeatherApiResponseDTO.class);
    
    System.out.println(response.getBody().getResponse().getBody().getItems().getItem().get(0).getCategory());
    同様に値を取得できます.
    書き終わったら、ページの実行ボタンをクリックすると、パケットされhtmlに送信されます.

    また、定期的なAPI要求のスケジューリング機能とデータベース収集の平均値を表示するページを作成します.