❤ 김춘장이의 위키백과 - 나만의 공부 기록 Tistory ❤
응급구조사에게 가장 핵심 서비스는 응급실찾기
이다
사용자에게 필요한 정보는
- 필요한 의료 서비스 정보
- 직관적으로 확인할 수 있는 위치
- 필요 의료 서비스를 받을 수 있는 병원의 필수 정보
의료서비스 정보
의료서비스의 분류는 국립중앙의료원에서 제공하는 항목을 기준으로 진행하였다.
드랍다운 메뉴방식으로 구성하였으며
여러 서비스를 선택하다 보면, 어떤 서비스를 선택했는지 알 수 있도록 병원 검색 버튼 위에
선택한 서비스가 버튼식으로 생성되게 진행했다.
추가로 해당 서비스가 불 필요 할 경우 생성된 버튼을 삭제 시 제거되도록 진행하였다
입원실 일반 격리 버튼을 삭제하고 응급전용 중환자실 서비스 선택
사용자에게 좀 더 직관적으로 보일 수 있도록 구성하였다.
메뉴 구성에 대한 기본 로직 소스코드이다
<section>
<div style="width: 100%; height: 800px">
<div id="map" style="filter: blur(0px);"
class="blurred-map"></div>
<div class="overlay">
<div class="shadow p-3 mb-5 bg-body-tertiary rounded" style="width: 100%; height: 750px; ">
<p class="fs-1"> 🧑⚕️ 의료 서비스 🧑⚕️</p>
<p class="fs-3"> 필요한 응급실을 찾아드립니다 </p>
<div class="select__menu">
<select class="form-select form-select-lg mb-3" aria-label="Large select example">
<option selected>입원 병상 선택 </option>
<option value="hv36">[응급전용] 입원실 </option>
<option value="hvec">[응급실] 일반병상 </option>
<option value="hvgc">[입원실] 일반 </option>
<option value="hv38">[입원실] 외상 전용</option>
<option value="hv4">[입원실] 정형외과 외과입원실 </option>
<option value="hv5">[입원실] 신경과 입원실 </option>
<option value="hv40">[입원실] 정신과 폐쇄병동 </option>
</select>
<select class="form-select form-select-lg mb-3" aria-label="Large select example">
<option selected> 중환자실 목록 </option>
<option value="hv31">[응급전용] 중환자실 </option>
<option value="hvicc">[중환자실] 일반 </option>
<option value="hvccc">[중환자실] 흉부외과 </option>
<option value="hvncc">[중환자실] 신생아 </option>
<option value="hvcc">[중환자실] 신경과 </option>
<option value="hv2">[중환자실] 내과 </option>
<option value="hv3">[중환자실] 외과 </option>
<option value="hv6">[중환자실] 신경외과 </option>
<option value="hv7">[중환자실] 약물 중환자 </option>
<option value="hv8">[중환자실] 화상 </option>
<option value="hv9">[중환자실] 외상 </option>
<option value="hv34">[중환자실] 심장내과 </option>
</select>
<select class="form-select form-select-lg mb-3" aria-label="Large select example">
<option selected>소아/유아 격리포함 </option>
<option value="hv10">VENTI(소아) </option>
<option value="hv11">인큐베이터(보육기) </option>
<option value="hv28">소아 </option>
<option value="hv32">[중환자실] 소아 </option>
<option value="hv33">[응급전용] 소아 중환자실 </option>
<option value="hv37">[응급전용] 소아 입원실 </option>
<option value="hv15">소아 - 일반 격리 </option>
<option value="hv16">소아 - 음압 격리 </option>
<option value="hv11">인큐베이터(보육기) </option>
</select>
<select class="form-select form-select-lg mb-3" aria-label="Large select example">
<option selected> 수술 및 기타 </option>
<option value="hvoc"> 수술실 </option>
<option value="hv39"> [외상전용] 수술실</option>
<option value="hv42"> 분만실 </option>
<option value="hv43"> 화상 전용 처치실 </option>
</select>
<select class="form-select form-select-lg mb-3" aria-label="Large select example">
<option selected> 필요 의료 장비 </option>
<option value="hvctayn"> CT </option>
<option value="hvmriayn"> MRI </option>
<option value="hvangioayn"> 혈관 촬영기 </option>
<option value="hvventiayn"> 인공 호흡기 </option>
<option value="hvventisoayn"> 조산아 인공 호흡기 </option>
<option value="hvincuayn"> 인큐베이터 </option>
<option value="hvcrrtayn"> CRRT </option>
<option value="hvecmoayn"> ECMO </option>
<option value="hvoxyayn"> 고압 산소 치료기 </option>
<option value="hvhypoayn"> 중심 체온 조절 유도기 </option>
</select>
<select class="form-select form-select-lg mb-3" aria-label="Large select example">
<option selected> 격리/감염병/코호트 </option>
<option value="hv14"> [일반격리] 격리진료구역/일반격리병상 </option>
<option value="hv21"> [응급전용] 입원실 일반 격리 </option>
<option value="hv30"> 응급실 일반 격리 병상 </option>
<option value="hv22"> [감염] 전담병상/중환자실 </option>
<option value="hv23"> [감염] 전담병상 중환자실 내 음압격리병상 </option>
<option value="hv24"> [감염] 중증 병상 </option>
<option value="hv25"> [감염] 준-중증 병상 </option>
<option value="hv26"> [감염] 중등증 병상 </option>
<option value="hv27"> [코호트] 격리 </option>
<option value="hv13"> 격리 진료 구역 음압 격리 병상 </option>
<option value="hv17"> [응급전용] 중환자실 음압 격리 </option>
<option value="hv18"> [응급전용] 중환자실 일반 격리 </option>
<option value="hv19"> [응급전용] 입원실 음압 격리 </option>
</select>
</div>
<hr class="border border-primary border-3 opacity-75">
<div id="selectedOptions"></div>
<form action="/getMatchHospitalData" method="post">
<!-- 실제 전송되는 데이터 구간 -->
<div id="inputFields"></div>
<button type="submit" class="btn btn-primary" style="width: 100%;" id="sendBtn">병원검색</button>
</form>
</div>
</div>
</div>
</section>
여기 소스에서 중요한 것은
실제 전송되는 데이터 구간이다
사용자가 실제 검색하고자 하는 버튼들이 생긴 구간이 버튼이 생긴 구간인데
그 구간에 데이터를 가지고 전달을 한다.
<form action="/getMatchHospitalData" method="post">
<!-- 실제 전송되는 데이터 구간 -->
<div id="inputFields"></div>
<button type="submit" class="btn btn-primary" style="width: 100%;" id="sendBtn">병원검색</button>
</form>
method는 post이고 url은 /getMatchHospitalData이다
컨트롤러
/**
* 공공데이터포털 API 명세서 5번째 service
* 사용자가 요구하는 필요서비스 정보에 맞는 병원리스트 리턴
* JSON 형식 , 매번 파라미터 내용 및 갯수 다름 hashMap mapping
* @return : {@link EgytBassInfoInqireVO} 사용자 요구사항에 부합한 병원 목록을 List로 반환
*/
@RequestMapping(value= "/getMatchHospitalData", method = RequestMethod.POST)
public String getMatchHospitalData(Model model, @ModelAttribute("requestData") @RequestParam HashMap<String, Object> requestData){
logger.debug("@requestData : {} ", requestData);
List<EgytBassInfoInqireVO> list = prmService.prmMatchHospitalList(requestData);
model.addAttribute("list", list);
model.addAttribute("KAKAO_MAP_KEY", KAKAO_MAP_KEY);
return "paramedicSite/prmMatching";
}
병원을 조회할 때 사용할 공공데이터포털 API 가 중요하다
글쓴이 같은 경우 명세서를 출력해서 너덜너덜 해질 때까지 보고 POSTMAN으로 테스트도 해봤던 것 같다😱
내가 쓴 국립중앙의료원_전국 응급의료기관 정보 조회 서비스 open API 경우
9가지의 Url이 있었는데 모두 다른 출력값과 기능이 있는 url이었다.
그중 응급실 실시간 가용병상정보 조회 url과 응급의료기관 기본정보조회 url을 사용했는데
응급실 실시간 가용병상정보 조회는 병원관계자 회원가입을 할 때 병원 정보를 가져올 때 사용하며
또한 지금 응급실 찾는 기능에도 사용했다.
지금 사용하는 이유는 사용자가 원하는 서비스기능이 여기에 response 데이터로 들어오기 때문이다.
그런데 왜 또 다른 응급의료기관 기본정보조회 url을 사용하느냐?
이유는 응급실 실시간 가용병상정보 조회는 병원의 위, 경도를 주지 않는다
다음 page에서 화면에 노출시켜야 하는 정보 중 카카오지도 API를 사용하여 병원 별 위치를
표시해줘야 하는데 응급실 실시간 가용병상정보 조회 에선 위, 경도가 없기 때문에
카카오 API에서 위치를 찍을 수 없다.
그래서 응급실 실시간 가용병상정보 조회에 나오는 병원 기관코드를 기준으로
응급의료기관 기본정보조회를 하여 response 되는 위, 경도 병원정보를 기준으로
다음 page에서 데이터를 보여줄 수 있도록 구현하였다.
그래서 컨트롤러 소스 마지막 부분에 보면
model.addAttribute("list", list);
model.addAttribute("KAKAO_MAP_KEY", KAKAO_MAP_KEY);
return "paramedicSite/prmMatching";
무언가 리스트를 전달하고
그리고 카카오 API 키를 전달하고
JSP파일로 이동시킨다.
그 사이의 무언가
의 처리는 서비스단에서 진행한다
서비스 처리 로직 - 1
어떻게 보면 무식하게 처리한 걸 수도 있다… 😂
방법은 이러하다
현재 구성한 서비스는 서울/경기도 만 진행하기 때문에 지역 조회를 2번 하는데
- ‘경기도’를 검색하여 모든 병원 데이터를 가져온다
- DOM구조로 만든 뒤 사용자가 입력 한 데이터를 가지고 비교 한 뒤 일치된 병원 정보를 파싱 하여 객체화 하며 List로 반환한다
- ‘서울시’ 검색하여 모든 병원 데이터를 가져온다
- DOM구조로 만든 뒤 사용자가 입력 한 데이터를 가지고 비교 한 뒤 일치된 병원 hpid만 파싱하여 List로 반환한다
- 두 개의 List를 join 한다
- join 된 리스트를 가지고 경도,위도가 있는 데이터를 검색하여 받아온다
// 사용자에게 입력받는다
// 전 지역의 병원값을 받는다
// 사용자가 요구한 값과 매칭시킨다
// 매칭된 지역을 리턴해준다resultData
// 지도에 해당 위도와 경도를 뽑아서 지도를 그린다
// 사용자 리스트에 보여준다
public List<EgytBassInfoInqireVO> prmMatchHospitalList(HashMap<String, Object> resultData){
// 경기도로 먼저 값 받아오기
Document gyDom = returnDOM("경기도");
// 데이터 파싱
List<String> gyList = prmHospitalAPI.returnMatchHospital(gyDom, resultData);
// 서울특별시로 값 받아오기
Document seoulDom = returnDOM("서울특별시");
List<String> seoulList = prmHospitalAPI.returnMatchHospital(seoulDom, resultData);
// 두개의 리스트 조인
List<String> join = new ArrayList<>();
join.addAll(gyList);
join.addAll(seoulList);
// 경도 위도가 있는 데이터 받아오기
return prmHospitalAPI.resultHospitalList(join);
}
// 구급구조대 필요한 데이터로 파싱
// dom : api로 받아온 데이터를 document로 재조립된 객체
// resultData : 사용자 니즈가 들어가있는 객체 / Key 값이 사용자가 요구하는 값
public List<String> returnMatchHospital(Document dom, HashMap<String, Object> resultData) {
List<String> matchedHpid = new ArrayList<>();
try {
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
// 모든 item 요소 가져오기
NodeList items = (NodeList) xpath.evaluate("//item", dom, XPathConstants.NODESET);
for (int i = 0; i < items.getLength(); i++) {
Node item = items.item(i);
boolean allMatch = true;
// 각 사용자 요구 사항 확인
for (String key : resultData.keySet()) {
String value = xpath.evaluate(key, item);
// 값이 Y이거나 숫자이고 1 이상인 경우 검사
if (!value.equals("Y") && !(isNumeric(value) && Integer.parseInt(value) >= 1)) {
allMatch = false;
break;
}
}
// 일치하는 기관의 hpid 값만 가져옴
if (allMatch) {
String hpid = xpath.evaluate("hpid", item);
matchedHpid.add(hpid);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return matchedHpid;
}
서비스 처리 로직 - 2
1번에서 처리된 join된 정보를 가지고 2번째 api처리를 진행한다
- List <String> 객체의 hpid 목록을 매개변수로 전달받음
- List를 순회하며 List에 있는 item을 한 개씩 검색
- 매 검색결과를 EgytBassInfoInqireVO 객체에 저장하고 List <EgytBassInfoInqireVO> 리스트 추가
// join이후 (서울/경기도)데이터 합쳐진 객체들을 조회 후 객체 만들기
public List<EgytBassInfoInqireVO> resultHospitalList(List<String> hpidMap) {
List<EgytBassInfoInqireVO> resultList = new ArrayList<>();
try {
for (int i = 0; i < hpidMap.size(); i++) {
// 1. URL 생성
StringBuilder urlBuilder = new StringBuilder(String.valueOf(hospitalURL3));
urlBuilder.append("?" + "serviceKey=" + hospitalKEY); // 서비스키
urlBuilder.append("&" + URLEncoder.encode("HPID", "UTF-8") + "=" + URLEncoder.encode(hpidMap.get(i), "UTF-8")); // 병원hpid
urlBuilder.append("&" + URLEncoder.encode("pageNo", "UTF-8") + "=" + URLEncoder.encode("1", "UTF-8")); //페이지건수
urlBuilder.append("&" + URLEncoder.encode("numOfRows", "UTF-8") + "=" + URLEncoder.encode("1", "UTF-8")); /*목록 건수*/
// URL 매핑
URL url = new URL(urlBuilder.toString());
// URL 전송
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
//api응답값 - xml BufferedReader resultData;
// 응답값 확인
if (connection.getResponseCode() >= 200 && connection.getResponseCode() <= 300) {
// 응답헤더가 정상이라면 DOM 구조로 파싱하기위해 대입
resultData = new BufferedReader(new InputStreamReader(connection.getInputStream()));
} else {
// 응답헤더가 에러라면 에러응답값 대입
resultData = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
}
// 응답으로 온 resultData를 라인별로 재조합한다
StringBuilder recombination = new StringBuilder();
String line;
while ((line = resultData.readLine()) != null) {
recombination.append(line);
}
// io 리소스 반납
resultData.close();
connection.disconnect();
// EgytBassInfoInqireVO temp = returnEgyVO(hospitalDataAPI.returnDomData(recombination));
resultList.add(returnEgyVO(hospitalDataAPI.returnDomData(recombination)));
}
} catch (Exception e) {
e.printStackTrace();
}
return resultList;
}
소스가 너무 길어져서 순회하는 소스 코드만 포스팅했는데
try안에 for문을 진행하여 순회를 하도록 했다
결국 컨트롤러에게 돌아가는 리스트 객체의 내부 포맷은
// 구급구조대 노드 조립하여 객체 생성함
private EgytBassInfoInqireVO returnEgyVO(Document parsingData) {
// item 태그에 해당하는 NodeList를 가져옵니다.
NodeList items = parsingData.getElementsByTagName("item");
Element item = (Element) items.item(0);
EgytBassInfoInqireVO vo = new EgytBassInfoInqireVO();
// 각 필드에 해당하는 태그의 값을 가져와 VO에 설정합니다.
vo.setHpid(getTagValue("hpid", item));
vo.setWgs84Lon(getTagValue("wgs84Lon", item));
vo.setWgs84Lat(getTagValue("wgs84Lat", item));
vo.setDutyname(getTagValue("dutyName", item));
vo.setDutytel1(getTagValue("dutyTel1", item));
vo.setDutytel3(getTagValue("dutyTel3", item));
vo.setDutyAddr(getTagValue("dutyAddr", item));
return vo;
}
이렇게 되어있고
기관아이디/위도/경도/병원이름/대표전화번호/응급실전화번호/주소로 구성되어 있다.
그다음 페이지 구성은 다음 포스팅으로!
'👀 Side Project > Eᴍʙᴀᴅ (23.12~24.01)' 카테고리의 다른 글
프로젝트에서 쓴 annotation 정리하기 (0) | 2024.02.01 |
---|---|
응급구조사편_의료서비스 검색화면 및 로직2 (0) | 2024.01.31 |
이메일 발송 동작 구현 및 테스트 (2) | 2024.01.31 |
디자인, 이게 최선이였냐?! , 불호령 떨어진 디자인 개편 (1) | 2024.01.28 |
회원가입 구현 (1) | 2024.01.28 |