最近的項目中有使用到el做站內分詞搜索,簡單描述下業務邏輯:
首先項目是類似於博客的一個系統,在發布文章的時候有全公開,只針對某個部門公開 還有私密的;
分析 在用戶沒有登錄的時候只能 搜索到全部公開符合搜索框條件的文章,登錄之后 可以搜索到自己發布私密的和針對自己所在部門發布的或者是全部公開的文章
我使用的是 es springdata包
pom依賴
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-elasticsearch</artifactId> </dependency>
創建一個文檔對應的實體類
package com.cmbchina.ccd.itpm.search.entity; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import java.util.Date; /** * @Author zly * @Date 2019/11/5 14:40 */ @Document(indexName = "asset_article", type = "article") public class Article { @Id private String id; @Field(index = true, type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word") private String title; @Field(index = true, type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word") private String digest; @Field(index = true, type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word") private String comment; private Date issueTime; private String cover; private String isPublic; private Integer pubStatus; private Integer visit; private String authorId; public String getAuthorId() { return authorId; } public void setAuthorId(String authorId) { this.authorId = authorId; } public Integer getVisit() { return visit; } public void setVisit(Integer visit) { this.visit = visit; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDigest() { return digest; } public void setDigest(String digest) { this.digest = digest; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public Date getIssueTime() { return issueTime; } public void setIssueTime(Date issueTime) { this.issueTime = issueTime; } public String getCover() { return cover; } public void setCover(String cover) { this.cover = cover; } public String getIsPublic() { return isPublic; } public void setIsPublic(String isPublic) { this.isPublic = isPublic; } public Integer getPubStatus() { return pubStatus; } public void setPubStatus(Integer pubStatus) { this.pubStatus = pubStatus; } @Override public String toString() { return "Article{" + "id='" + id + '\'' + ", title='" + title + '\'' + ", digest='" + digest + '\'' + ", comment='" + comment + '\'' + ", issueTime=" + issueTime + ", cover='" + cover + '\'' + ", isPublic='" + isPublic + '\'' + ", pubStatus=" + pubStatus + ", visit=" + visit + ", authorId='" + authorId + '\'' + '}'; } }
編寫一個Dao層
package com.cmbchina.ccd.itpm.search.dao; import com.cmbchina.ccd.itpm.search.entity.Article; import org.elasticsearch.index.query.QueryBuilder; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.repository.ElasticsearchCrudRepository; /** * @Author zly * @Date 2019/11/5 15:55 */ public interface ArticleDao extends ElasticsearchCrudRepository<Article, String> { Page<Article> findByIsPublicLikeOrIsPublicIsAndPubStatusIsAndTitleOrCommentOrDigestLike(String departName, String isPublic, String pubStatus, String title, String comment, String digest, Pageable pageable); Page<Article> search(QueryBuilder query, Pageable pageable); }
ps:上面一個使用JPA傳統的命名方式查詢方法實現的簡單的查詢
后面主要記錄的是第二個方法使用
邏輯實現層代碼:
package com.cmbchina.ccd.itpm.search.service; import com.cmbchina.ccd.itpm.label.entity.User; import com.cmbchina.ccd.itpm.label.utils.SwitchObjectUtil; import com.cmbchina.ccd.itpm.search.dao.ArticleDao; import com.cmbchina.ccd.itpm.search.dto.ArticleDto; import com.cmbchina.ccd.itpm.search.entity.Article; import com.cmbchina.ccd.itpm.search.entity.Label; import com.cmbchina.ccd.itpm.search.mapper.LabelMapper; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.sort.SortOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * @Author zly * @Date 2019/11/5 16:43 */ @Service @SuppressWarnings("all") public class ArticleService { private Logger logger = LoggerFactory.getLogger(ArticleService.class); @Autowired private ArticleDao articleDao; @Autowired private LabelMapper labelMapper; @Autowired private RedisTemplate redisTemplate; @Value("${asset.uploadPath}") private String filePath; @Value("${asset.local}") private String local; private ObjectMapper mapper = new ObjectMapper(); public Map findByTitleLike(String keywords, int page, int size) throws Exception { User user = SwitchObjectUtil.getUser(redisTemplate.opsForValue().get("user")); Map<String, Object> map = new HashMap<>(); SearchSourceBuilder builder = new SearchSourceBuilder(); SearchRequest searchRequest = new SearchRequest("asset_article"); Pageable pageable = PageRequest.of(page - 1, size); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); Page<Article> articlePage = null; List<ArticleDto> articleDtoList = null; try { if (user != null) { //SELECT * FROM asset_article WHERE (isPublic LIKE "%應用開發一室%" or isPublic="1") // AND pubStatus="1" // and (`comment` like "%測試%" or title like "%測試%" OR digest like "%測試%")
//OR ( isPublic = "0" AND authorId="66666")
boolQuery .should(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("isPublic", user.getDeptName())) .should(QueryBuilders.termQuery("isPublic", "1"))) .must(QueryBuilders.termQuery("pubStatus", "1")) .must(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("comment", keywords)) .should(QueryBuilders.matchQuery("digest", keywords)) .should(QueryBuilders.matchQuery("title", keywords))) .should(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("isPublic", "0")) .must(QueryBuilders.termQuery("authorId", user.getEmployeeId()))); } else { //SELECT * FROM asset_article WHERE (isPublic="1") // AND pubStatus="1" // and (`comment` like "%測試%" or title like "%測試%" OR digest like "%測試%") BoolQueryBuilder must = boolQuery.must(QueryBuilders.termQuery("isPublic", "1")) .must(QueryBuilders.termQuery("pubStatus", "1")) .must(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("comment", keywords)) .should(QueryBuilders.matchQuery("digest", keywords)) .should(QueryBuilders.matchQuery("title", keywords))); } articlePage = articleDao.search(boolQuery, pageable); articleDtoList = new ArrayList<>(); for (Article article : articlePage.getContent()) { ArticleDto articleDto = new ArticleDto(); articleDto.setId(article.getId()); articleDto.setComment(article.getComment()); articleDto.setCover(local + article.getCover()); articleDto.setDigest(article.getDigest()); articleDto.setIsPublic(article.getIsPublic()); articleDto.setIssueTime(article.getIssueTime()); articleDto.setTitle(article.getTitle()); articleDto.setPubStatus(article.getPubStatus()); articleDto.setVisit(article.getVisit()); List<Label> labelNameByArticle = labelMapper.getLabelNameByArticle(article.getId()); articleDto.setLabels(labelNameByArticle); articleDtoList.add(articleDto); } } catch (BeansException e) { map.put("code", 500); map.put("message", "獲取數據異常"); logger.error("全文搜索數據異常"); return map; } map.put("code", 200); map.put("total", articlePage.getTotalElements()); map.put("message", "SUCCESS"); map.put("data", articleDtoList); return map; } public int deleteArticleById(String id) { try { articleDao.deleteById(id); return 1; } catch (Exception e) { logger.error("搜索服務根據ID刪除文章失敗{}", e.getMessage()); return 0; } } }
說明一下
SELECT * FROM asset_article WHERE (isPublic LIKE "%應用開發一室%" or isPublic="1") AND pubStatus="1" and (`comment` like "%測試%" or title like "%測試%" OR digest like "%測試%") OR ( isPublic = "0" AND authorId="66666")
es 整合java代碼實現
boolQuery .should(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("isPublic", user.getDeptName())) .should(QueryBuilders.termQuery("isPublic", "1"))) .must(QueryBuilders.termQuery("pubStatus", "1")) .must(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("comment", keywords)) .should(QueryBuilders.matchQuery("digest", keywords)) .should(QueryBuilders.matchQuery("title", keywords))) .should(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("isPublic", "0")) .must(QueryBuilders.termQuery("authorId", user.getEmployeeId())));
兩種邏輯都在注釋寫了sql語句作為對比
不久會更新說一下排序的效果