一、环境安装
1.1 下载解压Elasticsearch-7.x版本,越高越好,低版本有Log4j漏洞,Easy-es目前支持7.x
1.2 IK中文分词器
将对应Elasticsearch版本IK放进文件夹,Elasticsearch-7.6.1,ik对应版本就是7.6.1,不配置分词器,会将国家 解析为国、家。
1.3 运行Elasticsearch
1.4查看运行状态
http://127.0.0.1:9200/
1.5 修改密码(正式环境)
Multi Elasticsearch Head密码连接
http://127.0.0.1:9200/?auth_user=elastic&auth_password=xxx
1.6 使用可视化连接工具
需求简单,我用谷歌浏览器插件足够了 Multi Elasticsearch Head
二、可视化工具检索数据
2.1 查看表数据
2.2 查询数据
查询 标题包含中国通史的数据
三、Easy-es
3.1 pom文件
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>easy-es支持版本</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>你ES版本</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>你ES版本</version>
</dependency>
<dependency>
<groupId>org.dromara.easy-es</groupId>
<artifactId>easy-es-boot-starter</artifactId>
<version>easy-es支持版本</version>
</dependency>
3.2 springboot配置
easy-es:
enable: true #默认为true,若为false则认为不启用本框架
address : 60.205.186.155:9200 # es的连接地址,必须含端口 若为集群,则可以用逗号隔开 例如:127.0.0.1:9200,127.0.0.2:9200
username: elastic #若无 则可省略此行配置
password: #若无 则可省略此行配置
3.4 实体类
package com.huida.framework.es.domain;
import lombok.Data;
import org.dromara.easyes.annotation.IndexField;
import org.dromara.easyes.annotation.IndexId;
import org.dromara.easyes.annotation.IndexName;
import org.dromara.easyes.annotation.rely.Analyzer;
import org.dromara.easyes.annotation.rely.FieldType;
import org.dromara.easyes.annotation.rely.IdType;
/**
* titleContent
*/
@Data
@IndexName("title_content")
public class EsTitleContent {
/**
* es中的唯一id
*/
@IndexId(type = IdType.UUID)
private String id;
/**
* mysql主键
*/
private String businessId;
/**
* mysql表名
*/
private String tableName;
/**
* 跳转页面
*/
private String pages;
/**
* 标题
*/
@IndexField(fieldType = FieldType.TEXT, analyzer = Analyzer.IK_SMART, searchAnalyzer = Analyzer.IK_MAX_WORD)
private String title;
/**
* 内容
*/
@IndexField(fieldType = FieldType.TEXT, analyzer = Analyzer.IK_SMART, searchAnalyzer = Analyzer.IK_MAX_WORD)
private String content;
}
3.5 mapper
package com.huida.framework.es.mapper;
import com.huida.framework.es.domain.EsTitleContent;
import org.dromara.easyes.core.core.BaseEsMapper;
/**
* Mapper
* <p>
* Copyright © 2021 xpc1024 All Rights Reserved
**/
public interface EsTitleContentMapper extends BaseEsMapper<EsTitleContent> {
}
3.6 springboot包扫描
@EsMapperScan("com.huida.es.mapper")
3.7 查询实体
package com.huida.framework.es.domain;
import lombok.Data;
import lombok.NonNull;
import java.util.List;
@Data
public class EsTitleContentQuery {
/**
* 每页的数量
*/
private Integer pageSize;
/**
* 总页数
*/
private Integer pageNum;
/**
* 下一页sort
*/
private List<Object> nextSearchAfter;
/**
* 搜索关键字
*/
private String keyword;
}
3.8 业务实现代码,查询,初始化数据
public void selectTrainingCourseToEs() {
List<TrainingCourse> trainingCourseList = trainingCourseService.selectTrainingCourseToEs();
List<EsTitleContent> esTitleContentList = new ArrayList<>(trainingCourseList.size() * 2);
EsTitleContent item = null;
for (TrainingCourse trainingCourse : trainingCourseList) {
item = new EsTitleContent();
item.setSystemType(trainingCourse.getSystemType());
item.setTitle(trainingCourse.getClassName());
if (StringUtils.isNotBlank(trainingCourse.getContent())) {
//用hutool工具类会保存(空格字符  ; &ensp; &emsp;
item.setContent(Jsoup.parse(trainingCourse.getContent()).text());
}
item.setBusinessId(trainingCourse.getRecordId());
item.setTableName("training_course_new");
if (StringUtils.equals(trainingCourse.getType(), "1")) {
item.setModule("同心培训");
} else {
item.setModule("同心活动");
}
item.setDataJsonStr(JSON.toJSONString(trainingCourse));
esTitleContentList.add(item);
}
batchInsert(esTitleContentList);
}
public boolean createIndex() {
// 1.初始化-> 创建索引(相当于mysql中的表)
return esTitleContentMapper.createIndex();
}
public void deleteAllData() {
Wrapper<EsTitleContent> esTitleContentWrapper = new LambdaEsQueryWrapper<>();
esTitleContentMapper.delete(esTitleContentWrapper);
}
public void batchInsert(List<EsTitleContent> list) {
if (CollectionUtil.isNotEmpty(list)){
esTitleContentMapper.insertBatch(list);
}
}
public SAPageInfo<EsTitleContent> search(EsTitleContentQuery esTitleContentQuery) {
LambdaEsQueryWrapper<EsTitleContent> wrapper = new LambdaEsQueryWrapper<>();
wrapper.sortByScore(SortOrder.DESC);
wrapper.orderByDesc(EsTitleContent::getId);
// wrapper.eq(EsTitleContent::getSystemType, "bgtx");
//SQL搜索
if (StringUtils.isNotBlank(esTitleContentQuery.getKeyword())) {
wrapper.and(i -> i.match(EsTitleContent::getTitle, esTitleContentQuery.getKeyword(), 10f)
.or().match(EsTitleContent::getContent, esTitleContentQuery.getKeyword(), 5f));
return esTitleContentMapper.searchAfterPage(wrapper, esTitleContentQuery.getNextSearchAfter(), esTitleContentQuery.getPageSize());
}
return new SAPageInfo<>();
}
3.9 前端调用查询
{
"pageNum": 1,
"pageSize": 10,
"keyword": "2024",
"nextSearchAfter": null
}
// {
// "pageNum": 2,
// "pageSize": 10,
// "keyword": "2024",
// "nextSearchAfter": [
// "3b5332ed-7a9b-4ee5-b31a-1c5eadbd519a"
// ]
// }
四、前端搜索组件
<template>
<view class="container">
<view class="search-header">
<view class="input-box">
<view class="cuIcon-search"></view>
<input name="input" class="keyword" :focus="true" v-model="queryParams.keyword"
@input="inputChange" @focus="inputFocus" @confirm="onKeywordConfirm"
:placeholder="defaultKeyword.keyword" confirm-type="search"/>
<text v-if="queryParams.keyword" class="cuIcon-close del" @tap="clearKeyword"></text>
</view>
<view class="right" @tap="closeSearch">取消</view>
</view>
<view class="search-result" v-if="searchResultOpen && searchResultList">
<u-cell-group>
<view class="result-box">
<view class="result-item" v-for="item in searchResultList" :key="item.id" @click="clickResult(item)">
<text class="train-type">
{{ item.module }}
</text>
<view class="title u-line-1" v-html="item.title"></view>
<view v-html="item.content"></view>
</view>
</view>
</u-cell-group>
</view>
<view class="search-result-empty" v-if="searchResultOpen && searchResultList.length===0">
<image class="icon" :src="`${TrainFileBasUrl}/app_resource/portal/static/images/zgt/search.png`"></image>
<text class="text">换个关键词试试</text>
</view>
<view class="padding-tb" v-if="searchResultOpen && searchResultList.length>0">
<u-loadmore :status="loadStatus" :load-text="loadText"/>
</view>
</view>
</template>
<script>
import tools from '@/commons/tools.js';
export default {
data() {
return {
queryParams: {
keyword: '',
pageNum: 1,
pageSize: 10,
nextSearchAfter: null,
hasData: true
},
//搜索结果展示
searchResultOpen: false,
//搜索结果
searchResultList: [],
//帮助搜索词
helpKeyword: [],
//历史搜索词
historyKeyword: [],
//默认搜索词
defaultKeyword: '',
//热门搜索词
hotKeyword: [],
//过滤搜索结果
categoryFilter: false,
filterCategory: [],
//排序字段
currentSortType: 'default',
//排序方式
currentSortOrder: 'desc',
loadStatus: 'loadmore',
loadText: {
loadmore: '加载更多',
loading: '正在加载...',
nomore: '没有更多数据了'
},
}
},
onReachBottom() {
if (this.queryParams.hasData) {
this.queryParams.pageNum += 1
this.getSearchResultList()
}
},
onShow() {
tools.checkSession()
},
methods: {
clickResult: function (item) {
if (item.tableName === 'tz_ly_book') {
tools.openPage('/pagesBook/pages/book/view', item.title, {
id: item.businessId
});
}
},
closeSearch: function () {
tools.back()
},
clearKeyword: function () {
this.queryParams = {
keyword: '',
pageNum: 1,
pageSize: 10,
nextSearchAfter: null,
hasData: true
}
this.searchResultOpen = false
this.searchResultList = []
},
inputFocus: function () {
this.searchResultOpen = false
this.searchResultList = []
if (this.keyword) {
this.getHelpKeyword();
}
},
inputChange: function (e) {
this.keyword = e.detail.value
this.searchResultOpen = false
this.getHelpKeyword();
},
onKeywordConfirm(event) {
this.getSearchResult(event.detail.value);
},
getSearchResult(keyword) {
this.keyword = keyword
this.queryParams.pageNum = 1
this.queryParams.nextSearchAfter = null
this.getSearchResultList();
},
getSearchResultList() {
this.loadStatus = 'loading';
tools.post(tools.Server + '/es/titleContent/search', this.queryParams, {
isPostData: true,
showLoading: false
}).then((res) => {
if (res.success) {
if (this.queryParams.pageNum < res.data.pages) {
this.loadStatus = 'loadmore';
this.queryParams.hasData = true;
this.queryParams.nextSearchAfter = res.data.nextSearchAfter
} else {
this.loadStatus = 'nomore';
this.queryParams.hasData = false;
}
if (res.data.list) {
if (1 === this.queryParams.pageNum) {
this.searchResultList = res.data.list
} else {
res.data.list.forEach(item => {
this.searchResultList.push(item)
})
}
}
this.searchResultOpen = true
}
})
},
getSearchKeyword() {
// let that = this;
// util.request(api.SearchIndex).then(function(res) {
// if (res.errno === 0) {
// that.historyKeyword = res.data.historyKeywordList
// that.defaultKeyword = res.data.defaultKeyword
// that.hotKeyword = res.data.hotKeywordList
// }
// });
},
getHelpKeyword: function () {
let that = this;
// util.request(api.SearchHelper, {
// keyword: that.keyword
// }).then(function(res) {
// if (res.errno === 0) {
// that.helpKeyword = res.data;
// }
// });
},
},
onLoad: function () {
this.getSearchKeyword();
}
}
</script>
<style lang="scss">
page {
min-height: 100%;
background-color: #f4f4f4;
}
.container {
min-height: 100%;
background-color: #f4f4f4;
}
.search-header {
position: fixed;
top: 44px;
width: 750rpx;
height: 91rpx;
display: flex;
background: #fff;
border-bottom: 1px solid rgba(0, 0, 0, .15);
padding: 0 31.25rpx;
font-size: 29rpx;
color: #333;
}
.search-header .input-box {
position: relative;
margin-top: 16rpx;
float: left;
width: 0;
flex: 1;
height: 59rpx;
line-height: 59rpx;
padding: 0 20rpx;
background: #f4f4f4;
}
.search-header .icon {
position: absolute;
top: 14rpx;
left: 20rpx;
width: 31rpx;
height: 31rpx;
}
.search-header .del {
position: absolute;
top: 3rpx;
right: 10rpx;
width: 53rpx;
height: 53rpx;
z-index: 10;
}
.search-header .keyword {
position: absolute;
top: 0;
left: 40rpx;
width: 506rpx;
height: 59rpx;
padding-left: 30rpx;
}
.search-header .right {
margin-top: 24rpx;
margin-left: 31rpx;
margin-right: 6rpx;
width: 58rpx;
height: 43rpx;
line-height: 43rpx;
float: right;
}
.search-result {
margin-top: 90rpx;
}
.no-search {
height: auto;
overflow: hidden;
margin-top: 91rpx;
}
.serach-keywords {
background: #fff;
width: 750rpx;
height: auto;
overflow: hidden;
margin-bottom: 20rpx;
}
.serach-keywords .h {
padding: 0 31.25rpx;
height: 93rpx;
line-height: 93rpx;
width: 100%;
color: #999;
font-size: 29rpx;
}
.serach-keywords .title {
display: block;
width: 120rpx;
float: left;
}
.serach-keywords .icon {
margin-top: 19rpx;
float: right;
display: block;
margin-left: 511rpx;
height: 55rpx;
width: 55rpx;
}
.serach-keywords .b {
width: 750rpx;
height: auto;
overflow: hidden;
padding-left: 31.25rpx;
}
.serach-keywords .item {
display: inline-block;
width: auto;
height: 48rpx;
line-height: 48rpx;
padding: 0 15rpx;
border: 1px solid #999;
margin: 0 31.25rpx 31.25rpx 0;
font-size: 24rpx;
color: #333;
}
.serach-keywords .item.active {
color: #b4282d;
border: 1px solid #b4282d;
}
.shelper-list {
width: 750rpx;
height: auto;
overflow: hidden;
background: #fff;
padding: 0 31.25rpx;
}
.shelper-list .item {
height: 93rpx;
width: 687.5rpx;
line-height: 93rpx;
font-size: 24rpx;
color: #333;
border-bottom: 1px solid #f4f4f4;
}
.search-result-empty {
width: 100%;
height: 100%;
padding-top: 300rpx;
}
.search-result-empty .icon {
margin: 0 auto;
display: block;
width: 240rpx;
height: 240rpx;
}
.search-result-empty .text {
display: block;
width: 100%;
height: 40rpx;
font-size: 28rpx;
text-align: center;
color: #999;
}
.result-box {
padding-bottom: 1px;
.result-item {
margin: 10px 12px;
background-color: #f1f1f1;
border-radius: 8px;
padding: 10px;
position: relative;
overflow: hidden;
.train-type {
color: #F96345;
border: 1px solid #F96345;
border-radius: 4rpx;
padding: 2px 5px;
}
.status-name {
background-color: #FBB653;
position: absolute;
z-index: 1;
top: 0px;
right: 0px;
color: #fff;
font-size: 12px;
padding: 4px 10px 3px;
border-bottom-left-radius: 8px;
}
.title {
color: #000;
font-weight: 600;
// font-size: 16px;
padding: 8px 0;
}
.org,
.date-time {
// font-size: 14px;
color: #878787;
padding: 3px 0;
}
.state {
position: absolute;
right: 0;
top: 40%;
padding: 5px 15px;
border-radius: 15px 0 0 15px;
// color: #FBB653;
// background-color: #FFF9F0;
}
.data-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 6px;
.sign-up {
background-color: #F1F1F1;
padding: 4px 5px;
// font-size: 13px;
color: #97a2a4;
border-radius: 3px;
.number {
color: #cb3f48;
margin-left: 5px;
}
}
.btn-box {
display: flex;
justify-content: flex-end;
.cu-btn {
margin-left: 10px;
}
}
}
}
}
</style>