From dae3c5b24ab09ed2fac29a947075a325179fcd99 Mon Sep 17 00:00:00 2001 From: guoxin <371864209@qq.com> Date: Thu, 21 Sep 2023 02:35:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=EF=BC=9A=E7=BB=BC=E5=90=88?= =?UTF-8?q?=E6=B5=8F=E8=A7=88=E5=A2=9E=E5=8A=A0=E6=90=9C=E7=B4=A2=E6=8E=A8?= =?UTF-8?q?=E8=8D=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../keyware/shandan/BrowserApplication.java | 6 +- .../shandan/browser/SearchLogProcessor.java | 139 ++++++++++++++++++ .../browser/controller/ReportController.java | 1 - .../browser/controller/SearchController.java | 1 + .../controller/SearchSuggestController.java | 34 +++++ .../src/main/resources/static/css/browser.css | 112 ++++++++++++-- .../src/main/resources/static/js/browser.js | 84 ++++++++++- .../src/main/resources/view/browser.html | 57 ++++--- .../browser/config/BianmuDataCache.java | 0 .../browser/config/BianmuDataCacheConfig.java | 0 .../browser/config/BrowserWebMvcConfig.java | 0 .../browser/entity/FullSearchParam.java | 0 .../shandan/browser/entity/PageVo.java | 0 .../browser/entity/SearchConditionVo.java | 6 + .../browser/entity/SearchResultRow.java | 0 .../browser/entity/SearchResultSort.java | 0 .../shandan/browser/enums/ConditionLogic.java | 0 .../shandan/browser/enums/ViewType.java | 0 .../shandan/frame/aspect/AppLogAspect.java | 39 ++++- .../shandan/system/entity/SysOperateLog.java | 2 + .../main/resources/static/js/sys/dict/dict.js | 4 +- 21 files changed, 438 insertions(+), 47 deletions(-) create mode 100644 shandan-browser/src/main/java/com/keyware/shandan/browser/SearchLogProcessor.java create mode 100644 shandan-browser/src/main/java/com/keyware/shandan/browser/controller/SearchSuggestController.java rename {shandan-browser => shandan-system}/src/main/java/com/keyware/shandan/browser/config/BianmuDataCache.java (100%) rename {shandan-browser => shandan-system}/src/main/java/com/keyware/shandan/browser/config/BianmuDataCacheConfig.java (100%) rename {shandan-browser => shandan-system}/src/main/java/com/keyware/shandan/browser/config/BrowserWebMvcConfig.java (100%) rename {shandan-browser => shandan-system}/src/main/java/com/keyware/shandan/browser/entity/FullSearchParam.java (100%) rename {shandan-browser => shandan-system}/src/main/java/com/keyware/shandan/browser/entity/PageVo.java (100%) rename {shandan-browser => shandan-system}/src/main/java/com/keyware/shandan/browser/entity/SearchConditionVo.java (96%) rename {shandan-browser => shandan-system}/src/main/java/com/keyware/shandan/browser/entity/SearchResultRow.java (100%) rename {shandan-browser => shandan-system}/src/main/java/com/keyware/shandan/browser/entity/SearchResultSort.java (100%) rename {shandan-browser => shandan-system}/src/main/java/com/keyware/shandan/browser/enums/ConditionLogic.java (100%) rename {shandan-browser => shandan-system}/src/main/java/com/keyware/shandan/browser/enums/ViewType.java (100%) diff --git a/shandan-browser/src/main/java/com/keyware/shandan/BrowserApplication.java b/shandan-browser/src/main/java/com/keyware/shandan/BrowserApplication.java index edf05a7..5316fae 100644 --- a/shandan-browser/src/main/java/com/keyware/shandan/BrowserApplication.java +++ b/shandan-browser/src/main/java/com/keyware/shandan/BrowserApplication.java @@ -1,7 +1,9 @@ package com.keyware.shandan; +import com.keyware.shandan.browser.SearchLogProcessor; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistryImpl; @@ -15,7 +17,9 @@ import org.springframework.security.core.session.SessionRegistryImpl; @SpringBootApplication public class BrowserApplication { public static void main(String[] args) { - SpringApplication.run(BrowserApplication.class, args); + ApplicationContext context = SpringApplication.run(BrowserApplication.class, args); + SearchLogProcessor processor = context.getBean(SearchLogProcessor.class); + processor.start(); } /** diff --git a/shandan-browser/src/main/java/com/keyware/shandan/browser/SearchLogProcessor.java b/shandan-browser/src/main/java/com/keyware/shandan/browser/SearchLogProcessor.java new file mode 100644 index 0000000..660d787 --- /dev/null +++ b/shandan-browser/src/main/java/com/keyware/shandan/browser/SearchLogProcessor.java @@ -0,0 +1,139 @@ +package com.keyware.shandan.browser; + +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.keyware.shandan.common.util.StringUtils; +import com.keyware.shandan.system.entity.SysOperateLog; +import com.keyware.shandan.system.service.SysOperateLogService; +import com.keyware.shandan.system.utils.DictUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 用于处理分析系统热词和用户搜索历史 + */ +@Slf4j +@Component +public class SearchLogProcessor extends Thread { + private final SysOperateLogService logService; + private int cycle = 7; + private DateField unit = DateField.DAY_OF_MONTH; + private final Map systemWords = new HashMap<>(); + + public SearchLogProcessor(SysOperateLogService logService) { + this.logService = logService; + } + + @Override + public void run() { + log.info("搜索日志处理程序启动成功"); + while (true) { + parseConfig(); + systemWords.clear(); + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + DateTime date = DateUtil.offset(new Date(), unit, cycle); + query.gt(SysOperateLog::getOperateTime, date.toJdkDate()); + logService.list(query).forEach(logs -> { + String word = parseLogs(logs); + if (StringUtils.hasText(word)) { + addSystemWords(word); + } + }); + try { + Thread.sleep(1000 * 60 * 30); + } catch (InterruptedException ignore) { + } + } + } + + /** + * 获取用户的搜索历史 + * + * @param loginName + * @return + */ + public List getUserSearchHistory(String loginName) { + List searchHistory = new ArrayList<>(); + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + query.eq(SysOperateLog::getLoginName, loginName).orderByDesc(SysOperateLog::getOperateTime).last("limit 30"); + for (SysOperateLog logs : logService.list(query)) { + String word = parseLogs(logs); + if (StringUtils.hasText(word) && !searchHistory.contains(word)) { + searchHistory.add(word); + } + } + return searchHistory; + } + + /** + * 获取系统级热词 + * + * @return + */ + public List getSystemWords() { + return systemWords + .entrySet() + .stream() + //.sorted(Comparator.comparingInt(Map.Entry::getValue)) + .sorted((a, b) -> b.getValue() - a.getValue()) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + + + private String parseLogs(SysOperateLog logs) { + String value = null; + String url = logs.getUrl(); + if (url.startsWith("/browser/search/")) { + String paramsText = logs.getParams(); + if (JSONUtil.isJsonArray(logs.getParams())) { + for (Object object : JSONArray.parseArray(paramsText)) { + JSONObject json = (JSONObject) object; + String fieldName = json.getString("fieldName"); + if ("metadataName".equals(fieldName)) { + value = json.getString("fieldValue"); + break; + } + } + } else { + JSONObject json = JSONObject.parseObject(paramsText); + if (json.containsKey("search")) { + value = json.getString("search"); + } + } + + } + return value; + } + + private void addSystemWords(String word) { + Integer count = systemWords.get(word); + count = count == null ? 0 : count; + systemWords.put(word, ++count); + } + + private void parseConfig() { + String systemWordsConfig = DictUtil.getPublicDict("browser_system_words_config"); + if (StringUtils.hasText(systemWordsConfig)) { + systemWordsConfig = systemWordsConfig.toLowerCase(); + if (systemWordsConfig.endsWith("d")) { + cycle = -Math.abs(Integer.parseInt(systemWordsConfig.replace("d", ""))); + unit = DateField.DAY_OF_YEAR; + } else if (systemWordsConfig.endsWith("m")) { + cycle = -Math.abs(Integer.parseInt(systemWordsConfig.replace("m", ""))); + unit = DateField.MONTH; + } else if (systemWordsConfig.endsWith("y")) { + cycle = -Math.abs(Integer.parseInt(systemWordsConfig.replace("y", ""))); + unit = DateField.YEAR; + } + } + } +} diff --git a/shandan-browser/src/main/java/com/keyware/shandan/browser/controller/ReportController.java b/shandan-browser/src/main/java/com/keyware/shandan/browser/controller/ReportController.java index 0cbc258..eb2a77b 100644 --- a/shandan-browser/src/main/java/com/keyware/shandan/browser/controller/ReportController.java +++ b/shandan-browser/src/main/java/com/keyware/shandan/browser/controller/ReportController.java @@ -11,7 +11,6 @@ import com.keyware.shandan.browser.config.BianmuDataCache; import com.keyware.shandan.browser.entity.SearchConditionVo; import com.keyware.shandan.browser.entity.ExportParam; import com.keyware.shandan.browser.entity.ReportVo; -import com.keyware.shandan.browser.service.ReportService; import com.keyware.shandan.browser.service.SearchService; import com.keyware.shandan.browser.service.impl.ReportDateServiceImpl; import com.keyware.shandan.browser.service.impl.ReportNumberServiceImpl; diff --git a/shandan-browser/src/main/java/com/keyware/shandan/browser/controller/SearchController.java b/shandan-browser/src/main/java/com/keyware/shandan/browser/controller/SearchController.java index 7621f96..2875bd1 100644 --- a/shandan-browser/src/main/java/com/keyware/shandan/browser/controller/SearchController.java +++ b/shandan-browser/src/main/java/com/keyware/shandan/browser/controller/SearchController.java @@ -187,6 +187,7 @@ public class SearchController { return Result.of(new PageVo()); } + @AppLog(operate = "检索数据资源") @PostMapping("/metadata/all/page/{directoryId}") public Result searchMetadataPage(@PathVariable String directoryId, SearchConditionVo condition) { return Result.of(metadataSearchService.searchByCondition(directoryId, condition)); diff --git a/shandan-browser/src/main/java/com/keyware/shandan/browser/controller/SearchSuggestController.java b/shandan-browser/src/main/java/com/keyware/shandan/browser/controller/SearchSuggestController.java new file mode 100644 index 0000000..31cf8db --- /dev/null +++ b/shandan-browser/src/main/java/com/keyware/shandan/browser/controller/SearchSuggestController.java @@ -0,0 +1,34 @@ +package com.keyware.shandan.browser.controller; + +import com.keyware.shandan.browser.SearchLogProcessor; +import com.keyware.shandan.common.entity.Result; +import com.keyware.shandan.frame.config.security.SecurityUtil; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 搜索建议前端控制器 + */ +@RestController +@RequestMapping("/search/suggest") +public class SearchSuggestController { + + private final SearchLogProcessor logProcessor; + + public SearchSuggestController(SearchLogProcessor logProcessor) { + this.logProcessor = logProcessor; + } + + @GetMapping("/user/history") + public Result getUserHistory() { + String loginName = SecurityUtil.getLoginUsername(); + return Result.of(logProcessor.getUserSearchHistory(loginName)); + } + + @GetMapping("/system/words") + public Result getSystemWords() { + return Result.of(logProcessor.getSystemWords()); + } +} diff --git a/shandan-browser/src/main/resources/static/css/browser.css b/shandan-browser/src/main/resources/static/css/browser.css index 93121e0..9d41bb6 100644 --- a/shandan-browser/src/main/resources/static/css/browser.css +++ b/shandan-browser/src/main/resources/static/css/browser.css @@ -1,24 +1,57 @@ .layui-card .layui-form-item { margin-bottom: 10 } -.layui-table-tool-self{top:auto; bottom: 10px;} + +.layui-table-tool-self { + top: auto; + bottom: 10px; +} + .layui-card .layui-form-item .layui-inline button { margin-top: 10px } + .layui-table-tool-temp { padding-right: 0; } -.dict-component .layui-form-select, .dict-component .layui-select-title{width: fit-content !important;} -.b-search-slide-div .layui-form-item .layui-input-inline{display: flex;flex-direction: row;} -.select-box .layui-form-select{position: unset} -.select-box .layui-form-select input, .dict-component .layui-select-title input{min-width: 260px;} -.select-box .layui-form-select{width: auto} -.select-box i.layui-edge, .dict-component i.layui-edge{right: 20px !important;} -.input-date.c-input{min-width: 340px} -.mark-selector, .mark-selector .layui-form-select, .mark-selector input{width: 340px !important;} -.layui-form-checkbox[lay-skin="primary"]{ + +.dict-component .layui-form-select, .dict-component .layui-select-title { + width: fit-content !important; +} + +.b-search-slide-div .layui-form-item .layui-input-inline { + display: flex; + flex-direction: row; +} + +.select-box .layui-form-select { + position: unset +} + +.select-box .layui-form-select input, .dict-component .layui-select-title input { + min-width: 260px; +} + +.select-box .layui-form-select { + width: auto +} + +.select-box i.layui-edge, .dict-component i.layui-edge { + right: 20px !important; +} + +.input-date.c-input { + min-width: 340px +} + +.mark-selector, .mark-selector .layui-form-select, .mark-selector input { + width: 340px !important; +} + +.layui-form-checkbox[lay-skin="primary"] { padding-left: 20px; } + .layui-form-checked[lay-skin="primary"] i { border-color: #5FB878 !important; background-color: #FFFFFF; @@ -30,7 +63,8 @@ padding-left: 1px; font-size: 10px; } -.layui-table-view .layui-form-checkbox[lay-skin="primary"] i{ + +.layui-table-view .layui-form-checkbox[lay-skin="primary"] i { width: 14px !important; height: 14px !important; margin-top: 2px; @@ -38,10 +72,60 @@ padding-left: 1px; font-size: 10px; } -.layui-tab-title{width: 100%} -.layui-tab-title .layui-form-checkbox.layui-form-checked span{ + +.layui-tab-title { + width: 100% +} + +.layui-tab-title .layui-form-checkbox.layui-form-checked span { color: #009688; } -li[data-key="markTag"]{ + +li[data-key="markTag"] { text-align: left; +} + +.hot-words-box { + background: #FFFFFF; + max-height: 500px; + position: absolute; + left: 124px; + border: 1px solid #dedede; + border-radius: 0 0 5px 5px; + z-index: 1000; + display: none; + padding: 5px 10px; +} + +.hot-words-box h6 { + font-weight: bold; +} + +.words-history, .system-words { +} + +.hot-words-box ul > li { + border-radius: 4px; + padding: 0 5px; +} + +.words-history ul > li { + color: #639; +} + +.hot-words-box ul > li:hover { + background: #f7f7f7; + overflow: hidden; + cursor: pointer; +} + +.mengban { + width: 100%; + display: none; + height: 100%; + position: fixed; + top: 0; + left: 0; + background: #0C0C0C47; + z-index: 800 } \ No newline at end of file diff --git a/shandan-browser/src/main/resources/static/js/browser.js b/shandan-browser/src/main/resources/static/js/browser.js index 161312f..12e73e2 100644 --- a/shandan-browser/src/main/resources/static/js/browser.js +++ b/shandan-browser/src/main/resources/static/js/browser.js @@ -160,6 +160,11 @@ layui.use(['layer', 'listPage', 'globalTree', 'gtable', 'form', 'element', 'drop } renderConditionTabByForm(true); } + + const suggest = new SearchSuggest(layui, 'searchKeyInput') + suggest.onClick((text) => { + + }) } }, }); @@ -199,7 +204,7 @@ layui.use(['layer', 'listPage', 'globalTree', 'gtable', 'form', 'element', 'drop $('#condition-btn').click(function () { const display = $('#condition-div').css('display'); if (display == 'none') { - $('#mengban').show().on('click', function () { + $('div[lay-id="dirMetadataTable"] .mengban').show().on('click', function () { $(this).hide(); $('#condition-btn').click(); }); @@ -222,7 +227,7 @@ layui.use(['layer', 'listPage', 'globalTree', 'gtable', 'form', 'element', 'drop $('#condition-cancel-btn').on('click', function () { $('#condition-div').slideToggle(100, 'linear') - $('#mengban').hide(); + $('div[lay-id="dirMetadataTable"] .mengban').hide(); }); // 监听条件标签删除事件 @@ -482,6 +487,7 @@ layui.use(['layer', 'listPage', 'globalTree', 'gtable', 'form', 'element', 'drop cols: THeadSetLayer.convertColumns(theadConfig, 'dirFileTable'), done: () => { form.val('full-search-form', {queryType}); + new SearchSuggest(layui, 'fileSearchKeyInput'); } }, }); @@ -512,6 +518,80 @@ layui.use(['layer', 'listPage', 'globalTree', 'gtable', 'form', 'element', 'drop }) +class SearchSuggest { + isHide = true; + // 系统级热词 + systemHotWords = []; + // 用户级历史搜索 + userHistoryWords = []; + + constructor(lay, elemId) { + this.layer = lay.layer; + this.$bindElem = $(`#${elemId}`); + this.$searchBtn = this.$bindElem.parent().find('button[lay-event="query"]'); + this.$shadeBox = $('.mengban'); + this.$elem = this.$bindElem.parent().find('.hot-words-box') + this.bindElem(); + // 获取系统级别的热词推荐 + Util.get(`/search/suggest/system/words`, {}, false).then(res => { + if (res.flag) { + this.systemHotWords = res.data; + } + }) + } + + bindElem() { + this.$bindElem.on('focus', () => this.show()) + // 获取系统级别的热词推荐 + Util.get(`/search/suggest/user/history`, {}, false).then(res => { + if (res.flag) { + this.userHistoryWords = res.data; + } + }) + } + + show() { + if (this.isHide) { + const e_width = this.$bindElem.outerWidth(), + e_height = this.$bindElem.outerHeight(), + e_border_width = this.$bindElem.css('border-left-width').replace('px', ''), + width = e_width - parseFloat(e_border_width); + this.$bindElem.css({'z-index': '999', position: 'relative'}) + this.$searchBtn.css({'z-index': '999', position: 'relative'}); + this.$elem.css({'top': `${e_height + 9}px`, 'width': width + 'px'}).show(); + this.$shadeBox.show().on('click', () => this.hide()); + this.$elem.find('.words-history ul').html(''); + for (let i = 0; i < Math.min(this.userHistoryWords.length, 5); i++) { + const word = this.userHistoryWords[i]; + this.$elem.find('.words-history ul').append(`
  • ${word}
  • `); + } + this.$elem.find('.system-words ul').html(''); + for (let i = 0; i < Math.min(this.systemHotWords.length, 5); i++) { + const word = this.systemHotWords[i]; + this.$elem.find('.system-words ul').append(`
  • ${word}
  • `); + } + this.$elem.find('ul li').on('click', (event) => { + const $li = $(event.target); + this.$bindElem.val($li.text()); + this.$searchBtn.click(); + }); + this.isHide = false; + } + } + + hide() { + this.$elem.hide(); + this.$shadeBox.hide(); + this.$bindElem.css('display', 'unset'); + this.isHide = true; + this.bindElem(); + } + + onClick(onclick) { + this.onclick = onclick; + } +} + /** * 表格表头配置组件 */ diff --git a/shandan-browser/src/main/resources/view/browser.html b/shandan-browser/src/main/resources/view/browser.html index 34c7fa1..11a75e8 100644 --- a/shandan-browser/src/main/resources/view/browser.html +++ b/shandan-browser/src/main/resources/view/browser.html @@ -27,7 +27,7 @@
      - +
    @@ -90,13 +90,24 @@
    - + +
    +
    +
    搜索历史
    +
      +
      +
      +
      热词推荐
      +
        +
        +
        - +
        -
        当前位置: