前端对应的页面如下,可以查询全部,目的地,攻略,游记,用户。
API模块 在trip_modules-api
父模块下创建子模块trip-search-api
,pom文件内容如下:
pom.xml <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.swx</groupId > <artifactId > trip-modules-api</artifactId > <version > 1.0.0</version > </parent > <artifactId > trip-search-api</artifactId > <properties > <maven.compiler.source > 11</maven.compiler.source > <maven.compiler.target > 11</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-elasticsearch</artifactId > </dependency > </dependencies > </project >
文档实体类 创建ES文档实体类:DestinationEs,首先创建com.swx.search.domain
包,在该包下创建文档实体类。
DestinationEs @Getter @Setter @Document(indexName = DestinationEs.INDEX_NAME) public class DestinationEs implements Serializable { public static final String INDEX_NAME = "destination" ; @Id @Field(type = FieldType.Long) private Long id; @Field(type = FieldType.Keyword) private String name; @Field(analyzer = "ik_max_word", searchAnalyzer = "ik_max_word", type = FieldType.Text) private String info; }
创建攻略搜索对象
StrategyEs @Getter @Setter @Document(indexName = StrategyEs.INDEX_NAME) public class StrategyEs implements Serializable { public static final String INDEX_NAME = "strategy" ; @Id @Field(type = FieldType.Long) private Long id; @Field(analyzer = "ik_max_word", searchAnalyzer = "ik_max_word", type = FieldType.Text) private String title; @Field(analyzer = "ik_max_word", searchAnalyzer = "ik_max_word", type = FieldType.Text) private String subTitle; @Field(analyzer = "ik_max_word", searchAnalyzer = "ik_max_word", type = FieldType.Text) private String summary; }
创建游记搜索对象
TravelEs @Getter @Setter @Document(indexName = TravelEs.INDEX_NAME) public class TravelEs implements Serializable { public static final String INDEX_NAME = "travel" ; @Id @Field(type = FieldType.Long) private Long id; @Field(analyzer = "ik_max_word", searchAnalyzer = "ik_max_word", type = FieldType.Text) private String title; @Field(analyzer = "ik_max_word", searchAnalyzer = "ik_max_word", type = FieldType.Text) private String summary; }
创建用户搜索对象
UserInfoEs @Getter @Setter @Document(indexName = UserInfoEs.INDEX_NAME) public class UserInfoEs implements Serializable { public static final String INDEX_NAME = "userinfo" ; @Id @Field(type = FieldType.Long) private Long id; @Field(type = FieldType.Keyword) private String city; @Field(analyzer = "ik_max_word", searchAnalyzer = "ik_max_word", type = FieldType.Text) private String info; }
微服务模块 在trip_modules
父模块下创建子模块trip-search-server
,pom文件内容如下:
pom.xml <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.swx</groupId > <artifactId > trip-modules</artifactId > <version > 1.0.0</version > </parent > <artifactId > trip-search-service</artifactId > <properties > <maven.compiler.source > 11</maven.compiler.source > <maven.compiler.target > 11</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > com.swx</groupId > <artifactId > trip-search-api</artifactId > <version > 1.0.0</version > </dependency > <dependency > <groupId > com.swx</groupId > <artifactId > trip-common-redis</artifactId > </dependency > <dependency > <groupId > com.swx</groupId > <artifactId > trip-users-api</artifactId > </dependency > <dependency > <groupId > com.swx</groupId > <artifactId > trip-article-api</artifactId > </dependency > <dependency > <groupId > commons-beanutils</groupId > <artifactId > commons-beanutils</artifactId > <version > 1.9.4</version > </dependency > </dependencies > </project >
配置文件:bootstrap.yaml
,内容如下:
spring: application: name: search-service cloud: nacos: server-addr: 124.221 .23 .47 :8848 config: file-extension: yaml namespace: trip_cloud_dev shared-configs: - data-id: redis-${spring.profiles.active}.yaml refresh: true profiles: active: dev
Nacos配置文件
配置网关路由
ID :trip-gateway-dev.yaml
Group :DEFAULT_GROUP
描述 :旅游项目网关配置
配置内容:
trip-gateway-dev.yaml - id: trip_search uri: lb://search-service predicates: - Path=/search/** filters: - StripPrefix=1
创建启动类:TripSearchApplication,首先创建com.swx.search
包,在该包下创建。
TripSearchApplication @EnableFeignClients @SpringBootApplication public class TripSearchApplication { public static void main (String[] args) { SpringApplication.run(TripSearchApplication.class, args); } }
ElasticsearchService 在包com.swx.search.parser
下创建 ElasticsearchTypeParser
传入一个函数,该函数会根据 id 查询数据库,返回一个实体对象
@FunctionalInterface public interface ElasticsearchTypeParser <T> { T parse (Class<T> clazz, String id) ; }
在包com.swx.search.utils
下创建 BeanUtils
public abstract class BeanUtils { public static void copyProperties (Object source, Object target) throws BeansException { copyProperties(source, target, (Class) null , (String[]) null ); } public static void copyProperties (Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) throws BeansException { Assert.notNull(source, "Source must not be null" ); Assert.notNull(target, "Target must not be null" ); Class<?> actualEditable = target.getClass(); if (editable != null ) { if (!editable.isInstance(target)) { throw new IllegalArgumentException ("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]" ); } actualEditable = editable; } PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null ; PropertyDescriptor[] var7 = targetPds; int var8 = targetPds.length; for (int var9 = 0 ; var9 < var8; ++var9) { PropertyDescriptor targetPd = var7[var9]; Method writeMethod = targetPd.getWriteMethod(); if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) { PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); if (sourcePd != null ) { Method readMethod = sourcePd.getReadMethod(); if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0 ], readMethod.getReturnType())) { try { if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true ); } Object value = readMethod.invoke(source); if (value == null ) { continue ; } if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true ); } writeMethod.invoke(target, value); } catch (Throwable var15) { throw new FatalBeanException ("Could not copy property '" + targetPd.getName() + "' from source to target" , var15); } } } } } } }
在包com.swx.search.service
下创建 ElasticsearchService
ElasticsearchService import com.swx.common.core.qo.QueryObject;import com.swx.search.parser.ElasticsearchTypeParser;import org.springframework.data.domain.Page;public interface ElasticsearchService { public void save (Object entity) ; public void save (Iterable<?> iterable) ; public void deleteById (String id, Class<?> clazz) ; <T> Page<T> searchWithHighlight (Class<?> esclz, Class<T> dtoclz, QueryObject qo, ElasticsearchTypeParser<T> parser, String... fields) ; }
在包com.swx.search.service.impl
下创建 ElasticsearchServiceImpl
import com.alibaba.fastjson2.JSON;import com.swx.common.core.qo.QueryObject;import com.swx.search.parser.ElasticsearchTypeParser;import com.swx.search.service.ElasticsearchService;import com.swx.search.utils.BeanUtils;import org.elasticsearch.index.query.MultiMatchQueryBuilder;import org.elasticsearch.index.query.QueryBuilders;import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.domain.*;import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;import org.springframework.data.elasticsearch.core.SearchHit;import org.springframework.data.elasticsearch.core.SearchHits;import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;@Service public class ElasticsearchServiceImpl implements ElasticsearchService { public static final Logger log = LoggerFactory.getLogger(ElasticsearchServiceImpl.class); @Autowired private ElasticsearchRestTemplate template; @Override public void save (Object entity) { template.save(entity); } @Override public void save (Iterable<?> iterable) { template.save(iterable); } @Override public void deleteById (String id, Class<?> clazz) { template.delete(id, clazz); } @Override public <T> Page<T> searchWithHighlight (Class<?> esclz, Class<T> dtoclz, QueryObject qo, ElasticsearchTypeParser<T> parser, String... fields) { MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(qo.getKeyword(), fields); HighlightBuilder highlightBuilder = new HighlightBuilder (); for (String field : fields) { highlightBuilder.field(field); } highlightBuilder.requireFieldMatch(false ); highlightBuilder.preTags("<span style='color:red'>" ); highlightBuilder.postTags("</span>" ); highlightBuilder.fragmentSize(800000 ); highlightBuilder.numOfFragments(0 ); Pageable pageable = PageRequest.of(qo.getCurrent() - 1 , qo.getSize(), Sort.Direction.ASC, "_id" ); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder () .withQuery(queryBuilder) .withPageable(pageable) .withHighlightBuilder(highlightBuilder) .build(); SearchHits<?> searchHits = template.search(searchQuery, esclz); List<T> list = new ArrayList <>(); for (SearchHit<?> searchHit : searchHits) { T target = parser.parse(dtoclz, searchHit.getId()); Map<String, String> map = highlightFieldsCopy(searchHit.getHighlightFields(), fields); try { T highlight = JSON.parseObject(JSON.toJSONString(map), dtoclz); BeanUtils.copyProperties(highlight, target); } catch (Exception e) { log.warn("[高亮搜索] 拷贝属性失败" , e); } list.add(target); } return new PageImpl <>(list, pageable, searchHits.getTotalHits()); } private Map<String, String> highlightFieldsCopy (Map<String, List<String>> map, String... fields) { Map<String, String> mm = new HashMap <>(); for (String field : fields) { List<String> hfs = map.get(field); if (hfs != null && !hfs.isEmpty()) { StringBuilder sb = new StringBuilder (); for (String hf : hfs) { sb.append(hf); } mm.put(field, sb.toString()); } } return mm; } }