(1)Windows 安装和使用 ElasticSearch
(2)【已解决】SpringBoot 整合 Spring Data Elasticsearch 启动报错 Bean 冲突
1.实现的后端接口
2.Maven依赖
<!-- Spring Data Elasticsearch 客户端 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId><!-- <version>${elasticsearch-client.version}</version> --> <!-- 使用默认的版本 -->
</dependency>
3.application.yml
spring:elasticsearch:uris: http://localhost:9200 # ES服务地址
# username: elastic
# password: xxx
4.操作 ES Index(索引)
4.1 Controller
package com.dragon.springboot3vue3.controller;import cn.dev33.satoken.util.SaResult;
import com.dragon.springboot3vue3.service.ESIndexService;
import com.dragon.springboot3vue3.utils.StringDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;@Tag(name = "ES-Index 接口")
@RestController
@RequestMapping("/es/index")
public class ESIndexController {@Autowiredprivate ESIndexService esIndexService;@Operation(summary = "创建ES索引")@PostMapping("/create")public SaResult create(@RequestBody StringDTO stringDTO) {String str = String.valueOf(esIndexService.create(stringDTO.getStr()));return SaResult.ok(str);}@Operation(summary = "删除ES索引")@DeleteMapping("/delete")public SaResult delete(@RequestBody StringDTO stringDTO) {String str = String.valueOf(esIndexService.delete(stringDTO.getStr()));return SaResult.ok(str);}@Operation(summary = "ES索引是否存在")@PostMapping("/exist")public SaResult exist(@RequestBody StringDTO stringDTO) {String str = String.valueOf(esIndexService.exist(stringDTO.getStr()));return SaResult.ok(str);}@Operation(summary = "根据索引名,获取ES索引详细信息")@PostMapping("/get")public SaResult get(@RequestBody StringDTO stringDTO) {return esIndexService.get(stringDTO.getStr());}@Operation(summary = "ES索引列表")@GetMapping("/getAll")public SaResult getAll() throws IOException {return esIndexService.getAll();}
}
4.2 Service
package com.dragon.springboot3vue3.service;import cn.dev33.satoken.util.SaResult;
import java.io.IOException;public interface ESIndexService {boolean create(String indexNme);boolean delete(String indexNme);boolean exist(String indexName);SaResult getAll() throws IOException;SaResult get(String indexName);
}
4.3 ServiceImpl
package com.dragon.springboot3vue3.service.impl;import cn.dev33.satoken.util.SaResult;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.indices.GetIndexResponse;
import com.dragon.springboot3vue3.service.ESIndexService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@Service
public class ESIndexServiceImpl implements ESIndexService {@Autowiredprivate ElasticsearchOperations elasticsearchOperations;@Autowiredprivate ElasticsearchClient elasticsearchClient;@Overridepublic boolean create(String indexName) {IndexOperations indexOps = elasticsearchOperations.indexOps(IndexCoordinates.of(indexName));if (!indexOps.exists()) {indexOps.create();return true;}return false; // 索引已存在,无需创建}@Overridepublic boolean delete(String indexName) {IndexOperations indexOps = elasticsearchOperations.indexOps(IndexCoordinates.of(indexName));if (indexOps.exists()) {indexOps.delete();return true;}return false; // 索引不存在,无法删除}@Overridepublic boolean exist(String indexName) {IndexOperations indexOps = elasticsearchOperations.indexOps(IndexCoordinates.of(indexName));return indexOps.exists();}@Overridepublic SaResult getAll() throws IOException {// * 匹配所有索引,-.* 排除以"."开头的GetIndexResponse response = elasticsearchClient.indices().get(b -> b.index("*,-.*"));// keySet() 提取 Map 中所有Key,并以Set返回List<String> list = response.result().keySet().stream().toList();// 遍历每个索引,将详细信息存入mapMap<String, Object> map = new HashMap<>();for (String indexName : list) {IndexOperations indexOps = elasticsearchOperations.indexOps(IndexCoordinates.of(indexName));map.put(indexName, indexOps.getInformation().getFirst());}return SaResult.ok().setData(map);}@Overridepublic SaResult get(String indexName) {IndexOperations indexOps = elasticsearchOperations.indexOps(IndexCoordinates.of(indexName));return SaResult.ok().setData(indexOps.getInformation().getFirst());}
}
5.操作 ES Document(文档)
5.1 Model
package com.dragon.springboot3vue3.es.model;import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.*;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;@Data
@Document(indexName = "articles") // 指定索引名称
//@Setting(shards = 3, replicas = 1) // 分片数和副本数
public class Articles implements Serializable {@Serialprivate static final long serialVersionUID = 1L;@Id // Elasticsearch文档ID@Field(type = FieldType.Keyword) // ID通常设为keyword类型private String id;@Field(type = FieldType.Keyword) // 分类ID适合用keyword类型private String categoryId;@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart") // 标题使用IK分词private String title;@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart") // 内容使用IK分词private String content;@Field(type = FieldType.Keyword) // URL通常不需要分词private String coverImg;@Field(type = FieldType.Keyword) // 状态适合用keyword类型private String status;@Field(type = FieldType.Keyword) // 创建人ID适合用keyword类型private String creatorId;@Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second) // ES日期格式@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;@Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second) // ES日期格式@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime ts;@Field(type = FieldType.Integer) // 逻辑删除(0:未删除,1:删除)private Integer deleteFlag = 0;
}
5.2 Controller
package com.dragon.springboot3vue3.controller;import cn.dev33.satoken.util.SaResult;
import com.dragon.springboot3vue3.controller.dto.pageDto.ArticlesPageDto;
import com.dragon.springboot3vue3.es.model.Articles;
import com.dragon.springboot3vue3.service.ArticlesService;
import com.dragon.springboot3vue3.utils.StringDTO;
import com.dragon.springboot3vue3.utils.StringsDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@Tag(name = "ES 文章接口")
@RestController
@RequestMapping("/es/articles")
public class ArticlesController {@Autowiredprivate ArticlesService articlesService;@Operation(summary = "新增或更新")@PostMapping("/saveOrUpdate")public SaResult saveOrUpdate(@RequestBody Articles articles) {return articlesService.saveOrUpdate(articles);}@Operation(summary = "根据ID查询")@PostMapping("/getById")public SaResult getById(@RequestBody StringDTO stringDTO) {return articlesService.getById(stringDTO.getStr());}@Operation(summary = "所有列表")@GetMapping("/getAll")public SaResult getAll() {return articlesService.getAll();}@Operation(summary = "分页列表")@PostMapping("/list")public SaResult list(@RequestBody ArticlesPageDto pageDto) {return articlesService.list(pageDto);}@Operation(summary = "全文搜索")@PostMapping("/search")public SaResult search(@RequestBody StringDTO stringDTO) {return articlesService.search(stringDTO.getStr());}@Operation(summary = "删除")@DeleteMapping("/delete")public SaResult deleteArticle(@RequestBody StringsDTO stringsDTO) {return articlesService.delete(stringsDTO.getStrings());}@Operation(summary = "逻辑删除")@PutMapping("/logicalDelete")public SaResult logicalDelete(@RequestBody StringDTO stringDTO) {return articlesService.logicalDelete(stringDTO.getStr());}@Operation(summary = "逻辑删除列表")@GetMapping("/logicalDeleteList")public SaResult logicalDeleteList() {return articlesService.logicalDeleteList();}}
5.3 Service
package com.dragon.springboot3vue3.service;import cn.dev33.satoken.util.SaResult;
import com.dragon.springboot3vue3.controller.dto.pageDto.ArticlesPageDto;
import com.dragon.springboot3vue3.es.model.Articles;
import java.util.Collection;public interface ArticlesService {SaResult saveOrUpdate(Articles articles);SaResult getById(String id);SaResult search(String keyword);SaResult getAll();SaResult list(ArticlesPageDto pageDto);SaResult logicalDelete(String id);SaResult delete(Collection<String> ids);SaResult logicalDeleteList();
}
5.4 ServiceImpl
package com.dragon.springboot3vue3.service.impl;import cn.dev33.satoken.util.SaResult;
import com.dragon.springboot3vue3.controller.dto.pageDto.ArticlesPageDto;
import com.dragon.springboot3vue3.es.model.Articles;
import com.dragon.springboot3vue3.es.repository.ArticlesRepository;
import com.dragon.springboot3vue3.service.ArticlesService;
import com.dragon.springboot3vue3.utils.ESPageResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;@Service
public class ArticlesServiceImpl implements ArticlesService {@Autowiredprivate ArticlesRepository articlesRepository;@Autowiredprivate ElasticsearchOperations elasticsearchOperations;@Overridepublic SaResult saveOrUpdate(Articles articles) {if (articles.getId() == null) {articles.setCreateTime(LocalDateTime.now());}articles.setTs(LocalDateTime.now());articlesRepository.save(articles);return SaResult.ok();}@Overridepublic SaResult getById(String id) {return SaResult.ok().setData(articlesRepository.findById(id));}@Overridepublic SaResult search(String keyword) {return SaResult.ok().setData(articlesRepository.findByContentContaining(keyword));}@Overridepublic SaResult getAll() {List<Articles> list = new ArrayList<>();articlesRepository.findAll().forEach(item->{if(item.getDeleteFlag()==0){list.add(item);}});return SaResult.ok().setData(list);}@Overridepublic SaResult list(ArticlesPageDto pageDto) {// 1. 构建查询条件Criteria criteria = new Criteria("deleteFlag").is(0);// 标题查询// ES 版本不够,不能使用 Articles::getTitleif (StringUtils.isNotBlank(pageDto.getTitle())) {criteria.and(new Criteria("title").contains(pageDto.getTitle()));}// 内容查询if (StringUtils.isNotBlank(pageDto.getContent())) {criteria.and(new Criteria("content").contains(pageDto.getContent()));}// 日期范围查询if (StringUtils.isNotBlank(pageDto.getStartDate()) && StringUtils.isNotBlank(pageDto.getEndDate())) {LocalDateTime start = LocalDateTime.parse(pageDto.getStartDate(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));LocalDateTime end = LocalDateTime.parse(pageDto.getEndDate(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));criteria.and(new Criteria("createTime").between(start, end));}// 2. 排序字段Sort sort = Sort.by(pageDto.getSortField());sort = "asc".equalsIgnoreCase(pageDto.getSortOrder()) ? sort.ascending() : sort.descending();// 3. 构建查询Query query = new CriteriaQuery(criteria).setPageable(PageRequest.of(pageDto.getPage(), pageDto.getSize())).addSort(sort);// 4. 执行查询SearchHits<Articles> searchHits = elasticsearchOperations.search(query, Articles.class);// 5. 处理结果List<Articles> articles = searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList());ESPageResponse response = new ESPageResponse(articles,searchHits.getTotalHits(),pageDto.getPage(),pageDto.getSize(),(int) Math.ceil((double) searchHits.getTotalHits() / pageDto.getSize()));return SaResult.ok().setData(response);}@Overridepublic SaResult logicalDelete(String id) {// 1. 根据ID查找文章Optional<Articles> articlesOptional = articlesRepository.findById(id);// 2. 检查文章是否存在if (!articlesOptional.isPresent()) {return SaResult.error("文章不存在");}// 3. 获取文章对象Articles article = articlesOptional.get();// 4. 检查是否已被删除(避免重复操作)if (article.getDeleteFlag() == 1) {return SaResult.error("文章已被删除");}article.setDeleteFlag(1);articlesRepository.save(article);return SaResult.ok();}@Overridepublic SaResult delete(Collection<String> ids) {articlesRepository.deleteAllById(ids);return SaResult.ok();}@Overridepublic SaResult logicalDeleteList() {List<Articles> list = new ArrayList<>();articlesRepository.findAll().forEach(item->{if(item.getDeleteFlag()==1){list.add(item);}});return SaResult.ok().setData(list);}
}
5.5 Repository
package com.dragon.springboot3vue3.es.repository;import com.dragon.springboot3vue3.es.model.Articles;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import java.util.List;@Repository
public interface ArticlesRepository extends ElasticsearchRepository<Articles, String> {List<Articles> findByContentContaining(String keyword);
}
5.6 其他
package com.dragon.springboot3vue3.utils;import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;@Schema(description = "ES分页响应数据")
@Data
@AllArgsConstructor
public class ESPageResponse<T> {private List<T> data; // 当前页数据private long total; // 总记录数private int currentPage; // 当前页码private int pageSize; // 每页大小private int pages; // 总页数
}
package com.dragon.springboot3vue3.utils;import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;@Data
public class StringDTO {@NotEmpty@Schema(description = "字符串")private String str;
}
package com.dragon.springboot3vue3.utils;import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.Collection;@Data
public class StringsDTO {@NotEmpty@Schema(description = "字符串数组")private Collection<String> strings;
}