增加图片检索功能

master
guoxin 1 year ago
parent 0af7c58b60
commit 5520a6a07e
  1. 12
      shandan-browser/src/main/java/com/keyware/shandan/browser/controller/SearchController.java
  2. 59
      shandan-browser/src/main/java/com/keyware/shandan/browser/service/FileSearchService.java
  3. 49
      shandan-browser/src/main/resources/static/css/browser.css
  4. 65
      shandan-browser/src/main/resources/static/js/browser.js
  5. BIN
      shandan-browser/src/main/resources/static/pic-item.png
  6. 16
      shandan-browser/src/main/resources/view/browser.html

@ -201,6 +201,18 @@ public class SearchController {
return Result.of(metadataSearchService.searchByCondition(directoryId, condition)); return Result.of(metadataSearchService.searchByCondition(directoryId, condition));
} }
/**
* 图片查询
* @return
*/
@GetMapping("/image/page/{dirId}")
public Result<Object> searchImage(@PathVariable String dirId,
@RequestParam(defaultValue = "") String searchText,
@RequestParam(defaultValue = "1") int pageNo,
@RequestParam(defaultValue = "20") int pageSize) throws IOException {
return Result.of(fileSearchService.searchImage(dirId, searchText, pageNo, pageSize));
}
/** /**
* 目录数据导出-创建任务 * 目录数据导出-创建任务
* *

@ -7,10 +7,13 @@ import com.keyware.shandan.bianmu.service.DirectoryService;
import com.keyware.shandan.browser.SearchLogProcessor; import com.keyware.shandan.browser.SearchLogProcessor;
import com.keyware.shandan.browser.entity.FullSearchParam; import com.keyware.shandan.browser.entity.FullSearchParam;
import com.keyware.shandan.browser.entity.PageVo; import com.keyware.shandan.browser.entity.PageVo;
import com.keyware.shandan.browser.entity.SearchResultRow;
import com.keyware.shandan.common.constants.DirConstant; import com.keyware.shandan.common.constants.DirConstant;
import com.keyware.shandan.common.util.StreamUtil; import com.keyware.shandan.common.util.StreamUtil;
import com.keyware.shandan.common.util.StringUtils; import com.keyware.shandan.common.util.StringUtils;
import com.keyware.shandan.system.entity.SysFile; import com.keyware.shandan.system.entity.SysFile;
import com.keyware.shandan.system.entity.SysSetting;
import com.keyware.shandan.system.service.SysSettingService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchResponse;
@ -18,7 +21,9 @@ import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Requests; import org.elasticsearch.client.Requests;
import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.search.sort.SortOrder;
@ -28,9 +33,8 @@ import org.springframework.stereotype.Service;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays; import java.util.stream.Collectors;
import java.util.List;
/** /**
* FileSearchService * FileSearchService
@ -41,6 +45,7 @@ import java.util.List;
@Slf4j @Slf4j
@Service @Service
public class FileSearchService { public class FileSearchService {
private final String[] imageTypes = {"apng", "avif", "jpeg", "jpg", "png", "svg", "webp", "bmp", "gif", "ico", "tiff"};
@Autowired @Autowired
private RestHighLevelClient esClient; private RestHighLevelClient esClient;
@ -48,6 +53,8 @@ public class FileSearchService {
private DirectoryService directoryService; private DirectoryService directoryService;
@Autowired @Autowired
private SearchLogProcessor searchLogProcessor; private SearchLogProcessor searchLogProcessor;
@Autowired
private SysSettingService settingService;
public PageVo searchFile(FullSearchParam param) throws IOException { public PageVo searchFile(FullSearchParam param) throws IOException {
SearchRequest request = Requests.searchRequest("shandan"); SearchRequest request = Requests.searchRequest("shandan");
@ -75,7 +82,7 @@ public class FileSearchService {
List<String> ids = new ArrayList<>(); List<String> ids = new ArrayList<>();
ids.add(metaId); ids.add(metaId);
ids.addAll(getChildrenByDirId(metaId)); ids.addAll(getChildrenByDirId(metaId));
builder.must(QueryBuilders.termsQuery("entityId", ids)); builder.must(QueryBuilders.termsQuery("entityId.keyword", ids));
} }
if (StringUtils.hasText(text)) { if (StringUtils.hasText(text)) {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
@ -127,8 +134,6 @@ public class FileSearchService {
} }
request.source(searchSourceBuilder); request.source(searchSourceBuilder);
// 设置排序
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT); SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
return PageVo.ofSearchHits(response.getHits(), param); return PageVo.ofSearchHits(response.getHits(), param);
} }
@ -137,4 +142,46 @@ public class FileSearchService {
DirectoryVo dir = directoryService.getById(dirId); DirectoryVo dir = directoryService.getById(dirId);
return StreamUtil.as(directoryService.childrenLists(dir, ReviewStatus.SUBMITTED)).map(DirectoryVo::getId).toList(); return StreamUtil.as(directoryService.childrenLists(dir, ReviewStatus.SUBMITTED)).map(DirectoryVo::getId).toList();
} }
public PageVo<Object> searchImage(String dirId, String searchText, int pageNo, int pageSize) throws IOException {
SearchRequest request = Requests.searchRequest("shandan");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.from(pageNo * pageSize - pageSize).size(pageSize);
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
queryBuilder.must(QueryBuilders.termsQuery("fileType", imageTypes));
if (!DirConstant.DIR_ROOT_ID.equals(dirId)) {
List<String> ids = new ArrayList<>();
ids.add(dirId);
ids.addAll(getChildrenByDirId(dirId));
queryBuilder.must(QueryBuilders.termsQuery("entityId.keyword", ids));
}
if (StringUtils.hasText(searchText)) {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.should(QueryBuilders.matchPhraseQuery("fileName", searchText));
queryBuilder.must(boolQuery);
}
searchSourceBuilder.query(queryBuilder);
request.source(searchSourceBuilder);
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SysSetting bianmuSetting = settingService.getById("BIANMU");
PageVo page = new PageVo();
page.setPage(pageNo);
page.setSize(pageSize);
SearchHits hits = response.getHits();
page.setTotal(hits.getTotalHits());
page.setPageTotal((int) Math.ceil(page.getTotal() / (double) page.getSize()));
page.setRecords(Arrays.stream(hits.getHits()).map(hit -> {
Map<String, Object> dataMap = hit.getSourceAsMap();
dataMap.put("title", dataMap.get("fileName"));
String path = ((String) dataMap.get("path")).replace("\\", "/");
dataMap.put("src", bianmuSetting.getSysAddress() + "/upload/" + path);
return dataMap;
}).collect(Collectors.toList()));
return PageVo.ofSearchHits(response.getHits(), page);
}
} }

@ -129,3 +129,52 @@ li[data-key="markTag"] {
background: #0C0C0C47; background: #0C0C0C47;
z-index: 800 z-index: 800
} }
.pic-search, .picture-container {
padding: 5px;
flex-wrap: wrap;
}
.pic-search input {
width: 300px;
}
.picture-container {
display: grid;
overflow-y: auto;
grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
grid-template-rows: repeat(auto-fit, minmax(200px, 1fr));
}
.picture-container .pic-item {
max-width: 100%;
max-height: 200px;
margin: 5px;
position: relative;
cursor: pointer;
}
.picture-container .pic-item:hover {
box-shadow: 0 0 4px 0;
}
.pic-item img {
width: 100%;
height: 100%;
}
.pic-item p {
position: absolute;
left: 0;
bottom: 0;
background: #ffffff78;
width: calc(100% - 20px);
padding: 0 10px;
overflow: hidden;
}
.layui-flow-more{
display: flex;
align-items: center;
flex-direction: column;
align-self: center;
}

@ -8,7 +8,7 @@
*/ */
// 目录树数据缓存 // 目录树数据缓存
const dirCache = new Map(); const dirCache = new Map();
layui.use(['layer', 'listPage', 'globalTree', 'gtable', 'form', 'element', 'dropdown', 'laydate', 'dict', 'datalabel'], function () { layui.use(['layer', 'listPage', 'globalTree', 'gtable', 'form', 'element', 'dropdown', 'laydate', 'dict', 'datalabel', 'flow'], function () {
const listPage = layui.listPage, const listPage = layui.listPage,
form = layui.form, form = layui.form,
element = layui.element, element = layui.element,
@ -77,31 +77,43 @@ layui.use(['layer', 'listPage', 'globalTree', 'gtable', 'form', 'element', 'drop
treeSearchEventListener() treeSearchEventListener()
} }
let activeDirectory = {};
/** /**
* 节点点击事件监听 * 节点点击事件监听
* @param node * @param node
*/ */
function nodeClickEventListener(node) { function nodeClickEventListener(node) {
let {basicData} = node.param; let {basicData} = node.param;
basicData = basicData || {id: DIR_ROOT_ID}; activeDirectory = basicData || {id: DIR_ROOT_ID};
//设置当前位置 //设置当前位置
setCurrentPosition(basicData); setCurrentPosition(activeDirectory);
if ($('#metaQuery').hasClass("layui-this")) { if ($('#metaQuery').hasClass("layui-this")) {
// 初始化数据资源表格 // 初始化数据资源表格
initMetadataTable(basicData.id) initMetadataTable(activeDirectory.id)
} }
if ($('#fileQuery').hasClass("layui-this")) { if ($('#fileQuery').hasClass("layui-this")) {
//初始化文件搜索表格 //初始化文件搜索表格
initFileSearchTable(basicData.id); initFileSearchTable(activeDirectory.id);
}
if ($('#pictureQuery').hasClass('layui-this')) {
$('#picSearchKey').val('')
queryImages(activeDirectory.id, '');
} }
} }
element.on('tab(metadataListCardBody)', function (data) { element.on('tab(metadataListCardBody)', function ({index}) {
if (!dirFileTable) { if (index === 1 && !dirFileTable) {
initFileSearchTable(DIR_ROOT_ID); initFileSearchTable(DIR_ROOT_ID);
} else if (index === 2) {
queryImages('ROOT', '')
} }
}); });
$('.pic-search button[lay-event="query"]').on('click', function () {
queryImages(activeDirectory.id, $('#picSearchKey').val())
})
/** /**
* 设置当前位置 * 设置当前位置
* @param basicData * @param basicData
@ -482,6 +494,45 @@ layui.use(['layer', 'listPage', 'globalTree', 'gtable', 'form', 'element', 'drop
dirFileTable.addTableRowEvent('theadSet2', () => tHeadSetLayer.show(fileTableId)) dirFileTable.addTableRowEvent('theadSet2', () => tHeadSetLayer.show(fileTableId))
} }
/**
* 流式加载图片
*/
function queryImages(dirId, searchText) {
let pageNo = 1, pageSize = 20;
$('#layer-photos').html('')
layui.flow.load({
elem: '#pic-container',
isAuto: true,
mb: 100,
done: function (page, next) { //执行下一页的回调
pageNo = page;
Util.get(`/search/image/page/${dirId}`, {pageNo, pageSize, searchText}).then(({flag, data, msg}) => {
if (flag) {
console.info(data);
const {records, pageTotal} = data, picItems = [];
for (const pic of records) {
const htm = `
<li class="pic-item">
<img layer-pid="${pic.id}" layer-src="${pic.src}" src="${pic.src}" alt="${pic.title}">
<p>${pic.title}</p>
</li>`;
picItems.push(htm);
}
next(picItems.join(''), pageNo < pageTotal);
layer.photos({
photos: '#pic-container',
anim: 5
});
} else {
showErrorMsg(msg);
}
}).catch(err => {
showErrorMsg(err);
});
}
});
}
}) })
class SearchSuggest { class SearchSuggest {

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

@ -79,6 +79,7 @@
<ul class="layui-tab-title"> <ul class="layui-tab-title">
<li class="layui-this metadata-table" id="metaQuery">资源查询</li> <li class="layui-this metadata-table" id="metaQuery">资源查询</li>
<li class="" id="fileQuery">文件查询</li> <li class="" id="fileQuery">文件查询</li>
<li class="" id="pictureQuery">图片查询</li>
</ul> </ul>
<div class="layui-tab-content"> <div class="layui-tab-content">
<div class="layui-tab-item layui-show"> <div class="layui-tab-item layui-show">
@ -250,6 +251,14 @@
</div> </div>
</script> </script>
</div> </div>
<div class="layui-tab-item">
<div class="current-position">当前位置:<label></label></div>
<div class="pic-search">
<input class="layui-input" id="picSearchKey" placeholder="输入关键字进行查询" autocomplete>
<button class="layui-btn layui-btn-sm" lay-event="query" id="imageSearchBtn">查询</button>
</div>
<ul class="picture-container" id="pic-container"></ul>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -260,15 +269,16 @@
<!-- js --> <!-- js -->
<script th:replace="common/head::static-foot"></script> <script th:replace="common/head::static-foot"></script>
<script th:src="@{/js/business/mark/markTag.js}"></script>
<script th:src="@{/js/browser.js}"></script>
<script th:src="@{/js/view.js}"></script>
<script type="text/javascript"> <script type="text/javascript">
const sizeInit = () => { const sizeInit = () => {
$(".layui-card-body").height(window.innerHeight - 20); $(".layui-card-body").height(window.innerHeight - 20);
$("#tree-toobar-div").height($(".layui-card-body").height() - 25 - 45); $("#tree-toobar-div").height($(".layui-card-body").height() - 25 - 45);
$(".picture-container").height($(".layui-card-body").height() - 140);
} }
window.onresize = sizeInit; window.onresize = sizeInit;
sizeInit(); sizeInit();
</script> </script>
<script th:src="@{/js/business/mark/markTag.js}"></script>
<script th:src="@{/js/browser.js}"></script>
<script th:src="@{/js/view.js}"></script>
</html> </html>