통합검색 추천검색어, 인기검색어, 연관검색어 기능 추가.

master
강석 최 2023-06-20 18:07:56 +09:00
parent f71dd2b273
commit 2a80d97d7e
9 changed files with 209 additions and 87 deletions

View File

@ -22,11 +22,10 @@ public class SearchEngineController {
private final SearchEngineService searchEngineService; private final SearchEngineService searchEngineService;
private final MenuMgtService menuMgtService; private final MenuMgtService menuMgtService;
private final CodeMgtService codeMgtService;
@GetMapping("/searchPage") @GetMapping("/searchPage")
public ModelAndView organMgtPage(@AuthenticationPrincipal UserInfo loginUser, SearchParams params) { public ModelAndView searchPage(@AuthenticationPrincipal UserInfo loginUser, SearchParams params) {
ModelAndView mav = new ModelAndView("searchEngine/search"); ModelAndView mav = new ModelAndView("searchEngine/searchPage");
if(Utils.isEmpty(params.getKeyword())){ if(Utils.isEmpty(params.getKeyword())){
mav.setViewName("common/keywordRequest"); mav.setViewName("common/keywordRequest");
return mav; return mav;
@ -44,22 +43,22 @@ public class SearchEngineController {
case "all": case "all":
params.setQueryInfo(); params.setQueryInfo();
params.setForm("search_menu_view.search_menu_view"); params.setForm("search_menu_view.search_menu_view");
result = searchEngineService.getData(params, SearchMenuView.class); result = searchEngineService.getSearchResult(params, SearchMenuView.class);
totalCnt += result.getTotalCount(); totalCnt += result.getTotalCount();
mav.addObject("menuResult", result); mav.addObject("menuResult", result);
params.setForm("search_board_view.search_board_view"); params.setForm("search_board_view.search_board_view");
result = searchEngineService.getData(params, SearchBoardView.class); result = searchEngineService.getSearchResult(params, SearchBoardView.class);
totalCnt += result.getTotalCount(); totalCnt += result.getTotalCount();
mav.addObject("boardResult", result); mav.addObject("boardResult", result);
params.setForm("search_file_view.search_file_view"); params.setForm("search_file_view.search_file_view");
result = searchEngineService.getData(params, SearchFileView.class); result = searchEngineService.getSearchResult(params, SearchFileView.class);
totalCnt += result.getTotalCount(); totalCnt += result.getTotalCount();
mav.addObject("fileResult", result); mav.addObject("fileResult", result);
break; break;
case "menu": case "menu":
params.setQueryInfo(); params.setQueryInfo();
params.setForm("search_menu_view.search_menu_view"); params.setForm("search_menu_view.search_menu_view");
result = searchEngineService.getData(params, SearchMenuView.class); result = searchEngineService.getSearchResult(params, SearchMenuView.class);
totalCnt += result.getTotalCount(); totalCnt += result.getTotalCount();
params.setContentCnt(totalCnt); params.setContentCnt(totalCnt);
mav.addObject("menuResult", result); mav.addObject("menuResult", result);
@ -68,7 +67,7 @@ public class SearchEngineController {
case "board": case "board":
params.setQueryInfo(); params.setQueryInfo();
params.setForm("search_board_view.search_board_view"); params.setForm("search_board_view.search_board_view");
result = searchEngineService.getData(params, SearchBoardView.class); result = searchEngineService.getSearchResult(params, SearchBoardView.class);
totalCnt += result.getTotalCount(); totalCnt += result.getTotalCount();
params.setContentCnt(totalCnt); params.setContentCnt(totalCnt);
mav.addObject("boardResult", result); mav.addObject("boardResult", result);
@ -77,7 +76,7 @@ public class SearchEngineController {
case "file": case "file":
params.setQueryInfo(); params.setQueryInfo();
params.setForm("search_file_view.search_file_view"); params.setForm("search_file_view.search_file_view");
result = searchEngineService.getData(params, SearchFileView.class); result = searchEngineService.getSearchResult(params, SearchFileView.class);
totalCnt += result.getTotalCount(); totalCnt += result.getTotalCount();
params.setContentCnt(totalCnt); params.setContentCnt(totalCnt);
mav.addObject("fileResult", result); mav.addObject("fileResult", result);
@ -89,9 +88,19 @@ public class SearchEngineController {
menuParam.setFirstIndex(0); menuParam.setFirstIndex(0);
menuParam.setRowCnt(Integer.MAX_VALUE); menuParam.setRowCnt(Integer.MAX_VALUE);
mav.addObject("menuList", menuMgtService.selectMenuMgtList(menuParam)); mav.addObject("menuList", menuMgtService.selectMenuMgtList(menuParam));
mav.addObject("suggestList", searchEngineService.getSuggestList(params.getKeyword()));
mav.addObject("rankList", searchEngineService.getRankingList());
mav.addObject("searchParams", params); mav.addObject("searchParams", params);
mav.addObject("totalCnt", totalCnt); mav.addObject("totalCnt", totalCnt);
return mav; return mav;
} }
@GetMapping("/suggestKeyword")
public ModelAndView suggestKeyword(SearchParams params){
ModelAndView mav = new ModelAndView("searchEngine/suggestKeyword");
mav.addObject("keywordList", searchEngineService.getKeywordList(params.getKeyword()));
return mav;
}
} }

View File

@ -0,0 +1,11 @@
package com.dbnt.faisp.main.searchEngine.model;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class SearchRank {
private String keyword;
private String movement;
}

View File

@ -1,14 +1,13 @@
package com.dbnt.faisp.main.searchEngine.service; package com.dbnt.faisp.main.searchEngine.service;
import com.dbnt.faisp.main.searchEngine.model.SearchMenuView; import com.dbnt.faisp.main.searchEngine.model.*;
import com.dbnt.faisp.main.searchEngine.model.SearchParams;
import com.dbnt.faisp.main.searchEngine.model.SearchResult;
import com.dbnt.faisp.util.Utils; import com.dbnt.faisp.util.Utils;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser; import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException; import org.json.simple.parser.ParseException;
@ -21,7 +20,6 @@ import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI; import java.net.URI;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -30,15 +28,92 @@ import java.util.List;
@Service @Service
public class SearchEngineService { public class SearchEngineService {
@Value("${search.engine.url}") @Value("${search.engine.default}")
private String engineUrl; private String defaultUrl;
@Value("${search.engine.suggest}")
private String suggestUrl;
public SearchResult getData(SearchParams params, Class<?> rowClass){ public SearchResult getSearchResult(SearchParams params, Class<?> rowClass){
JSONObject jsonObj = requestToSearchEngine(params); JSONObject jsonObj = getSearchResultJsonObject(params);
return convertJSONObjectToSearchResult(jsonObj, rowClass); return convertJSONObjectToSearchResult(jsonObj, rowClass);
} }
public List<String> getSuggestList(String keyword) {
URI uri = UriComponentsBuilder.fromHttpUrl(suggestUrl)
.path("/ksf/api/suggest")
.queryParam("target", "related")
.queryParam("domain_no", "0")
.queryParam("term", keyword)
.queryParam("max_count", "10")
.encode()
.build()
.toUri();
private JSONObject requestToSearchEngine(SearchParams params){ ResponseEntity<String> response = requestRestTemplateExchange(uri);
JSONArray jsonAry = null;
try {
jsonAry = (JSONArray) new JSONParser().parse(response.getBody());
} catch (ParseException e) {
e.printStackTrace();
}
List<String> suggestList = new ArrayList<>();
for(Object str: jsonAry){
suggestList.add(str.toString());
}
return suggestList;
}
public List<SearchRank> getRankingList() {
URI uri = UriComponentsBuilder.fromHttpUrl(suggestUrl)
.path("/ksf/api/rankings")
.queryParam("domain_no", "0")
.queryParam("max_count", "10")
.encode()
.build()
.toUri();
ResponseEntity<String> response = requestRestTemplateExchange(uri);
JSONArray jsonAry = null;
try {
jsonAry = (JSONArray) new JSONParser().parse(response.getBody());
} catch (ParseException e) {
e.printStackTrace();
}
List<SearchRank> rankList = new ArrayList<>();
for(Object row: jsonAry){
JSONArray rank = (JSONArray) row;
rankList.add(new SearchRank(rank.get(0).toString(), rank.get(1).toString()));
}
return rankList;
}
public List<String> getKeywordList(String keyword) {
URI uri = UriComponentsBuilder.fromHttpUrl(suggestUrl)
.path("/ksf/api/suggest")
.queryParam("target", "complete")
.queryParam("domain_no", "0")
.queryParam("term", keyword)
.queryParam("mode", "s")
.queryParam("max_count", "10")
.encode()
.build()
.toUri();
ResponseEntity<String> response = requestRestTemplateExchange(uri);
JSONArray jsonAry = null;
try {
jsonAry = (JSONArray) ((JSONArray)((JSONObject) new JSONParser().parse(response.getBody())).get("suggestions")).get(0);
} catch (ParseException e) {
e.printStackTrace();
}
List<String> keywordList = new ArrayList<>();
for(Object str: jsonAry){
keywordList.add(((JSONArray) str).get(0).toString());
}
return keywordList;
}
private JSONObject getSearchResultJsonObject(SearchParams params){
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("text_idx='") builder.append("text_idx='")
.append(params.getKeyword()) .append(params.getKeyword())
@ -60,7 +135,7 @@ public class SearchEngineService {
} }
} }
String where = builder.toString(); String where = builder.toString();
URI uri = UriComponentsBuilder.fromHttpUrl(engineUrl) URI uri = UriComponentsBuilder.fromHttpUrl(defaultUrl)
.path("/search5") .path("/search5")
.queryParam("select", "*") .queryParam("select", "*")
.queryParam("from", params.getForm()) .queryParam("from", params.getForm())
@ -75,22 +150,7 @@ public class SearchEngineService {
.build() .build()
.toUri(); .toUri();
RestTemplate restTemplate = new RestTemplateBuilder() ResponseEntity<String> response = requestRestTemplateExchange(uri);
.setConnectTimeout(Duration.ofMillis(5000))
.setReadTimeout(Duration.ofMillis(5000))
.build();
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_JSON);
header.setAccept(MediaType.parseMediaTypes(MediaType.ALL_VALUE));
header.setConnection("keep-alive");
RequestEntity<Void> req = RequestEntity
.get(uri)
.headers(header)
.build();
ResponseEntity<String> response = restTemplate.exchange(req, String.class);
JSONObject jsonObj = null; JSONObject jsonObj = null;
try { try {
jsonObj = (JSONObject) new JSONParser().parse(response.getBody()); jsonObj = (JSONObject) new JSONParser().parse(response.getBody());
@ -101,6 +161,8 @@ public class SearchEngineService {
return jsonObj; return jsonObj;
} }
private SearchResult convertJSONObjectToSearchResult(JSONObject jsonObj, Class<?> rowClass){ private SearchResult convertJSONObjectToSearchResult(JSONObject jsonObj, Class<?> rowClass){
JSONObject resultJSON = (JSONObject) jsonObj.get("result"); JSONObject resultJSON = (JSONObject) jsonObj.get("result");
SearchResult result = new SearchResult(); SearchResult result = new SearchResult();
@ -123,4 +185,24 @@ public class SearchEngineService {
result.setRowList(convertList); result.setRowList(convertList);
return result; return result;
} }
private ResponseEntity<String> requestRestTemplateExchange(URI uri){
RestTemplate restTemplate = new RestTemplateBuilder()
.setConnectTimeout(Duration.ofMillis(5000))
.setReadTimeout(Duration.ofMillis(5000))
.build();
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_JSON);
header.setAccept(MediaType.parseMediaTypes(MediaType.ALL_VALUE));
header.setConnection("keep-alive");
RequestEntity<Void> req = RequestEntity
.get(uri)
.headers(header)
.build();
ResponseEntity<String> response = restTemplate.exchange(req, String.class);
return response;
}
} }

View File

@ -12,8 +12,8 @@ spring.servlet.multipart.max-request-size=500MB
site.domain=http://localhost:8080 site.domain=http://localhost:8080
clipReport.print.url=http://118.219.150.34:50570/ClipReport5 clipReport.print.url=http://118.219.150.34:50570/ClipReport5
search.engine.url=http://118.219.150.34:7577 search.engine.default=http://118.219.150.34:7577
search.engine.suggest=http://118.219.150.34:7614
#file #file
file.dir.publicBoard=/publicBoard file.dir.publicBoard=/publicBoard
file.dir.faRpt=/faRpt file.dir.faRpt=/faRpt

View File

@ -30,7 +30,6 @@ input::-moz-placeholder { color: #aaa; }
#searchHeader .searchPagetop .searchBox > a{width:78px; position:absolute; top:5px; right:90px; background:#377fe7; color:#fff; text-align:center; display:block; height:44px; line-height:44px; border-radius:5px; border:1px solid #377fe7;} #searchHeader .searchPagetop .searchBox > a{width:78px; position:absolute; top:5px; right:90px; background:#377fe7; color:#fff; text-align:center; display:block; height:44px; line-height:44px; border-radius:5px; border:1px solid #377fe7;}
#searchHeader .searchPagetop .searchBox > a:last-of-type{right:5px; color:#377fe7; background:#fff; } #searchHeader .searchPagetop .searchBox > a:last-of-type{right:5px; color:#377fe7; background:#fff; }
#searchHeader .searchPagetop .searchBox .autoSearchList{position:absolute; top:56px; left:0; display:none; background:#fff; box-sizing:border-box; border:1px solid #bec8d8; border-top:none; z-index:9990; width:calc(100% - 175px);} #searchHeader .searchPagetop .searchBox .autoSearchList{position:absolute; top:56px; left:0; display:none; background:#fff; box-sizing:border-box; border:1px solid #bec8d8; border-top:none; z-index:9990; width:calc(100% - 175px);}
#searchHeader .searchPagetop .searchBox > input:focus ~ .autoSearchList{display:block;}
.autoSearchList > p{padding:20px; font-size:15px; color:#284d7e;} .autoSearchList > p{padding:20px; font-size:15px; color:#284d7e;}
.autoSearchList > ul{padding:13px 0;} .autoSearchList > ul{padding:13px 0;}

View File

@ -25,6 +25,39 @@ $(function () {
}); });
}); });
$(document).on('focus', '#keywordInput', function (){
if(this.value !== undefined && this.value !== "") {
$("#autoSearchList").css('display', 'block')
}else{
$("#autoSearchList").css('display', 'none')
}
})
$(document).on('input', '#keywordInput', function (){
if(this.value !== undefined && this.value !== ""){
$.ajax({
url: '/search/suggestKeyword',
data: {keyword: this.value},
type: 'GET',
dataType:"html",
success: function(html){
const autoSearchList = $("#autoSearchList")
autoSearchList.empty().append(html);
autoSearchList.css('display', 'block')
},
error:function(){
}
});
}else{
$("#autoSearchList").css('display', 'none')
}
})
$(document).on('click', '.suggestKeyword', function (){
$("#keywordInput").val(this.innerText);
})
$(document).on('click', '#searchBtn', function (){ $(document).on('click', '#searchBtn', function (){
searchPageFormSubmit(1); searchPageFormSubmit(1);
}) })

View File

@ -27,25 +27,7 @@
<div class="card-body"> <div class="card-body">
<form method="get" th:action="@{/faStatistics/illegalShipInfo}" id="cdsSearchForm"> <form method="get" th:action="@{/faStatistics/illegalShipInfo}" id="cdsSearchForm">
<input type="hidden" name="pageIndex" id="pageIndex" th:value="${searchParams.pageIndex}"> <input type="hidden" name="pageIndex" id="pageIndex" th:value="${searchParams.pageIndex}">
<div class="row justify-content-between py-1"> <div class="row justify-content-end py-1">
<div class="col-auto">
<div>
<select class="form-select form-select-sm" name="year">
<option value="">연도</option>
<th:block th:each="year : ${yearList}">
<option th:value="${year}" th:text="|${year}년|" th:selected="${searchParams.year eq year}"></option>
</th:block>
</select>
</div>
<div>
<select class="form-select form-select-sm" name="month">
<option value="">월 선택</option>
<th:block th:each="month : ${#numbers.sequence(1, 12)}">
<option th:value="${month}" th:text="|${month}월|" th:selected="${searchParams.month eq month}"></option>
</th:block>
</select>
</div>
</div>
<div class="col-8"> <div class="col-8">
<div class="row"> <div class="row">
<div class="col-11"> <div class="col-11">

View File

@ -21,25 +21,16 @@
<input type="hidden" name="activeTab" id="activeTabInput" th:value="${searchParams.activeTab}"> <input type="hidden" name="activeTab" id="activeTabInput" th:value="${searchParams.activeTab}">
<input type="hidden" name="wrtOrgan" th:value="${searchParams.wrtOrgan}"> <input type="hidden" name="wrtOrgan" th:value="${searchParams.wrtOrgan}">
<input type="hidden" name="wrtUserSeq" th:value="${searchParams.wrtUserSeq}"> <input type="hidden" name="wrtUserSeq" th:value="${searchParams.wrtUserSeq}">
<input type="text" title="입력" name="keyword" placeholder="검색어를 입력해주세요." th:value="${searchParams.keyword}"/> <input type="text" title="입력" id="keywordInput" name="keyword" placeholder="검색어를 입력해주세요." th:value="${searchParams.keyword}"/>
<a href="#" id="searchBtn">검색</a> <a href="#" id="searchBtn">검색</a>
<!--<div class="autoSearchList"> <div id="autoSearchList" class="autoSearchList">
&lt;!&ndash;<p>자동완성 기능입니다.(데이터 없을 경우 문구)</p>&ndash;&gt;
<ul> </div>
<li><a href="#">연관검색어 1</a></li>
<li><a href="#">연관검색어 2</a></li>
<li><a href="#">연관검색어 3</a></li>
<li><a href="#">연관검색어 4</a></li>
<li><a href="#">연관검색어 5</a></li>
<li><a href="#">연관검색어 6</a></li>
<li><a href="#">연관검색어 7</a></li>
</ul>
</div>-->
<a href="#" class="searchPageBoxBtn">검색옵션</a> <a href="#" class="searchPageBoxBtn">검색옵션</a>
<div class="sRightBox"> <div class="sRightBox">
<span> <span>
<input id="reSearch" type="checkbox" value="#" /> <!--<input id="reSearch" type="checkbox" value="#" />
<label for="reSearch">결과 내 재검색</label> <label for="reSearch">결과 내 재검색</label>-->
</span> </span>
</div> </div>
</div> </div>
@ -290,26 +281,32 @@
<section> <section>
<h3>추천검색어</h3> <h3>추천검색어</h3>
<ul class="myWord"> <ul class="myWord">
<li><a href="#">해양</a></li> <th:block th:each="suggest:${suggestList}">
<li><a href="#">수상안전</a></li> <li><a th:href="${#strings.concat('/search/searchPage?keyword=', suggest)}" th:text="${suggest}"></a></li>
<li><a href="#">안전교육</a></li> </th:block>
<li><a href="#">면허</a></li>
<li><a href="#">응급구조</a></li>
</ul> </ul>
</section> </section>
<section> <section>
<h3>인기검색어</h3> <h3>인기검색어</h3>
<ul class="popularWord"> <ul class="popularWord">
<li><span>1</span><a href="#">업무</a><b class="red"></b></li> <th:block th:each="rank,row:${rankList}">
<li><span>2</span><a href="#">수상구조사</a><b class="red">NEW</b></li> <li>
<li><span>3</span><a href="#">안전교육 일정</a><b class="blue"></b></li> <span th:text="${row.count}"></span>
<li><span>4</span><a href="#">자격증발급</a><b class="red"></b></li> <a href="#" th:text="${rank.keyword}"></a>
<li><span>5</span><a href="#">의무경찰</a><b class="red"></b></li> <th:block th:switch="${rank.movement}">
<li><span>6</span><a href="#">조종면허</a><b class="blue"></b></li> <b th:case="new" class="red">NEW</b>
<li><span>7</span><a href="#">안전사고</a><b class="blue"></b></li> <b th:case="0">-</b>
<li><span>8</span><a href="#">동력</a><b class="blue"></b></li> <th:block th:case="*">
<li><span>9</span><a href="#">응급구조</a><b class="red"></b></li> <th:block th:if="${#strings.contains(rank.movement, '-')}">
<li><span>10</span><a href="#">오염방제</a><b class="blue"></b></li> <b class="blue"></b>
</th:block>
<th:block th:unless="${#strings.contains(rank.movement, '-')}">
<b class="red"></b>
</th:block>
</th:block>
</th:block>
</li>
</th:block>
</ul> </ul>
</section> </section>
</div> </div>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<ul>
<th:block th:each="keyword:${keywordList}">
<!--<li><a th:href="${#strings.concat('/search/searchPage?keyword=', keyword)}" th:text="${keyword}"></a></li>-->
<li><a href="#" class="suggestKeyword" th:text="${keyword}"></a></li>
</th:block>
</ul>
</html>