본문 바로가기
Font-end/Javascript

[공적마스크API] 공적 마스크 판매 정보 조회

by 허도치 2020. 3. 18.

 

0. 서론

 최근 코로나19로 인해 정상적인 가격의 마스크를 구하기가 어려워졌습니다. 그래서 정부에서는 약국과 우체국 등을 통해 공적마스크를 판매하고 있습니다. 그리고, 공적마스크의 판매처와 판매현황을 알 수 있는 API를 건강보험심사평가원에서 제공하고 있습니다.

 이번 포스트에서는 이 API를 이용하여 특정 지역의 공적마스크 판매처와 판매현황을 확인할 수 있는 웹페이지를 만들어 보려고 합니다. 

 

 

 

1. 프로젝트 준비
1-1. API 데이터 확인

1) [GET] /stores/json
   - 약국, 우체국, 농협 등의 마스크 판매처 정보 제공 (마스크 재고 관련 정보는 제공하지 않음)
   - page=페이지 번호[default: 1]
   - perPage=한 페이지당 출력할 판매처 수[default: 500, min:500, max:5000]

2) [GET] /sales/json
   - 마스크 재고 상태 등의 판매 정보 제공(판매처 관련 정보는 제공하지 않음)
   - page=페이지 번호[default: 1]
   - perPage=한 페이지당 출력할 판매처 수[default: 500, min:500, max:5000]

3) [GET] /storesByGeo/json
   - 중심 좌표(위/경도)를 기준으로 반경(미터단위) 안에 존재하는 판매처 및 재고 상태 등의 판매 정보 제공
   - lat=위도(wgs84 좌표계) / 최소:33.0, 최대:43.0
   - lng=경도(wgs84 표준) / 최소:124.0, 최대:132.0
   - m=반경(미터) / 최대 5000(5km)까지 조회 가능

4) *[GET] /storesByAddr/json
   - 주소를 기준으로 해당 구 또는 동내에 존재하는 판매처 및 재고 상태 등의 판매 정보 제공.
   - 예- '서울특별시 강남구' or '서울특별시 강남구 논현동'
   - ('서울특별시' 와 같이 '시'단위만 입력하는 것은 불가능합니다.)
   - address=검색 기준이 될 주소

5) API URL 선정, '/storesByAddr/json'
   - 주소를 입력하여 판매처와 재고 상태를 한번에 확인할 수 있음.

6) 참고사이트
   - SwaggerHub
     : https://app.swaggerhub.com/apis-docs/Promptech/public-mask-info/20200307-oas3#/SaleResult
   - 공공데이터포털
     : https://www.data.go.kr/dataset/15043025/openapi.do

 

1-2. HTTP 통신 라이브러리

1) AXIOS
   - Promise를 기반의 HTTP통신 Javascript 라이브러리.
   - 요청을 취소할 수 있음.
   - 결과값을 자동으로 JSON데이터로 변환.
   - CSRF 보호.

2) CDN
   - <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

 

1-3. 프로젝트 구조

 

 

 

2. HTML 작성
2-1. index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="kr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>공적 마스크 판매처 조회</title>
  <link rel="stylesheet" href="./css/common.css">
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
  <div>
    <input type="text" id="search_text" value="" placeholder="예) 서울특별시 노원구">
    <button id="search_btn">찾기</button>
  </div>
  <div>
    <ul id="filter_list">
    </ul>
  </div>
  <div>
    <ul id="mask_list">
    </ul>
  </div>
  <script src="./js/common.js"></script>
</body>
</html>
cs

1) [ 8 ln ] - Axios 라이브러리 CDN

2) [ 12 ln ] - 주소 입력창

3) [ 13 ln ] - 검색 버튼

4) [ 16~17 ln ] - 필터링 조건 목록
   : Javascript를 통해 페이지가 로드된 후 동적으로 생성.

5) [ 20~21 ln ] - 검색 결과 목록
   : API를 통해 조회된 결과를 출력.

6) [ 23 ln ] - 스크립트 추가
   : 페이지가 로드된 후 적용하기 위해 body태그 안에 추가.

 

 

 

3. CSS 작성
3. css/common.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 입력창 설정 */
#search_text { width: 200px; }
 
/* 리스트 설정 */
#mask_list { list-style: decimal; }
#mask_list > li > a { display: inline-block;  }
 
/* 마스크 색상 지정 */
#mask_list > li > .plenty { background-color: green; }
#mask_list > li > .some { background-color: yellow; }
#mask_list > li > .few { background-color: red; }
#mask_list > li > .empty { background-color: gray; }
#mask_list > li > .break { background-color: darkgray; }
 
/* 출력 사이즈 설정 */
#mask_list > li > .mask-remain_stat { width: 150px; text-align: center; }
#mask_list > li > .mask-name { width: 150px; text-align: center; }
#mask_list > li > .mask-addr { text-align: left; }
cs

 

 

 

4. Javascript 작성
4-1. js/common.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// 공적마스크 판매현황 정보
const remain_stats = {
  "plenty": {
    "index"1,
    "label""100개 이상",
    "color""green"
  },
  "some": {
    "index"2,
    "label""30개 이상 100개미만",
    "color""yellow"
  },
  "few": {
    "index"3,
    "label""2개 이상 30개 미만",
    "color""red"
  },
  "empty": {
    "index"4,
    "label""1개 이하",
    "color""gray"
  },
  "break": {
    "index"5,
    "label""판매중지",
    "color""darkgray"
  }
}
 
// Element 탐색
const search_text = document.getElementById("search_text");
const search_btn = document.getElementById("search_btn");
const filter_list = document.getElementById("filter_list");
const mask_list = document.getElementById("mask_list");
 
// 입력창에서 엔터키 입력
search_text.addEventListener("keydown"function(event){
  ifevent.keyCode === 13 ){
    search_btn.click();
  }
});
 
// 검색 버튼 클릭 이벤트
search_btn.addEventListener("click", handleSearch);
 
// 검색 필터링 생성
Object.keys(remain_stats).forEach(function(key){
  const el_id = "filter-"+key;
 
  const li = document.createElement("li");
 
  const label_text = document.createTextNode(remain_stats[key].label);
  const label = document.createElement("label");
  label.setAttribute("for", el_id);
  label.appendChild(label_text);
 
  const checkbox = document.createElement("input");
  checkbox.type = "checkbox";
  checkbox.name = "filter";
  checkbox.value = key;
  checkbox.id = el_id;
  checkbox.checked = true;
 
  li.appendChild(label);
  li.appendChild(checkbox);
 
  filter_list.appendChild(li);
});
 
// 검색 버튼 이벤트 핸들러
async function handleSearch(event){
  event.preventDefault();
 
  // 기존 리스트 삭제
  Array.from(mask_list.children).forEach(item=>item.remove());
 
  // 선택된 필터링 조회
  const address = search_text.value;
  const filter = document.querySelectorAll('input[name="filter"]:checked');
  const filtering = Array.from(filter).map(function(checked){
    return checked.value;
  });
 
  // 데이터 가져오기
  const datas = await getDatas(address, filtering);
 
  // 리스트 렌더링
  datas.forEach(function(data){
    mask_list.appendChild(renderItem(data));
  })
}
 
// 리스트 아이템 생성
function renderItem(data){
  const li = document.createElement("li");
  const a_remain_stat = document.createElement("a");
  const a_name = document.createElement("a");
  const a_addr = document.createElement("a");
 
  a_remain_stat.className = "mask-remain_stat";
  a_name.className = "mask-name";
  a_addr.className = "mask-addr";
 
  const remain_data = remain_stats[data.remain_stat];
  a_remain_stat.appendChild(document.createTextNode(remain_data.label));
  a_remain_stat.classList.add(data.remain_stat);
  a_name.appendChild(document.createTextNode(data.name));
  a_addr.appendChild(document.createTextNode(data.addr));
 
  li.className = `mask-item mask-${data.remain_stat}`;
 
  li.appendChild(a_remain_stat);
  li.appendChild(a_name);
  li.appendChild(a_addr);
 
  return li;
}
 
// API에 데이터 요청하기
function getDatas(address, filtering){
  const server_url = "https://8oi9s0nnth.apigw.ntruss.com/corona19-masks/v1";
  const api_uri = "/storesByAddr/json";
  
  const data = axios({
    method: "GET",
    url: server_url + api_uri,
    params: {
      "address": address
    },
    headers: {
      "Content-Type""application/json"
    }
  }).then(function(result){
    const stores = result.data.stores;
 
    // 필터링 조건에 일치하는 데이터 추출
    const filtered = !!filtering ? stores.filter(function(info){
      return filtering.indexOf(info.remain_stat) !== -1;
    }) : stores;
 
    // 데이터 정렬
    const sorted = filtered.sort(function(a, b){
      const a_index = remain_stats[a.remain_stat].index;
      const b_index = remain_stats[b.remain_stat].index;
 
      return a_index > b_index ? 1 : -1;
    });
 
    return sorted;
  }).catch(function(error){
    console.error(error);
    return [];
  });
 
  return data;
}
cs

1) [ 1~28 ln ] - 공적마스크 재고 정보
   : 각 색상은 지정된 색상이므로 그대로 사용하는 것을 권장.

2) [ 37~41 ln ] - 입력창에 엔터키 이벤트 적용

3) [ 47~68 ln ] - 필터링 목록 생성
   : 공적마스크 재고 정보를 통해 필터링 목록 생성.

4) [ 71-91 ln ] - 검색 버튼 이벤트 핸들러
   : 검색버튼을 눌렀을 때, 입력한 주소와 선택된 필터링 목록을 추출.
   : 추출된 정보를 통해 데이터 수집.
   : 수집된 데이터를 통해 리스트를 렌더링.

5) [ 94-117 ln ] - 리스트 아이템 Element 생성
   : 수집된 정보를 통해 리스트 아이템을 동적으로 생성.

6) [ 120~156 ln ] - API 데이터를 수집
   : 수집된 데이터를 필터링하고 정렬하여 반환.

 

 

 

5. 실행
5-1. index.html 실행

 

 

 

마치며

- 지역별로 공적마스크 판매처와 판매현황을 조회하는 웹페이지를 만들어보았다.
- API를 이용하여 화면을 간단하게 그려주는 예제라서 디자인은 예쁘지 않으므로, 출력되는 정보에 집중하길 바란다.
- 코로나를 이겨내자!

댓글