1.数据批量导入
@Test void testBatchAddDocment() throws Exception { int pageNo = 0; while (true) { //取第一页10条数据 Page<Item> page = Page.of(pageNo, 100); //查询所有商品信息 Page<Item> itemPage = itemService.page(page, new LambdaQueryWrapper<Item>()); List<Item> items = itemPage.getRecords(); if (CollectionUtils.isEmpty(items)) { break; } //拷贝属性 List<ItemDoc> itemDocs = BeanUtils.copyList(items, ItemDoc.class); //批量添加请求 BulkRequest.Builder br = new BulkRequest.Builder(); itemDocs.forEach(itemDoc -> br.operations(op -> op .index(i -> i .index("items197") .id(itemDoc.getId().toString()) .document(itemDoc)))); //构建请求 BulkRequest build = br.build(); //批量添加 BulkResponse bulkResponse = esClient.bulk(build); pageNo++; } }
2.开发搜索服务
1.工程创建
<?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.hmall</groupId> <artifactId>hmall-parent</artifactId> <version>1.0.0</version> </parent> <artifactId>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> <!--nacos 服务注册发现--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--feign模块--> <dependency> <groupId>com.hmall</groupId> <artifactId>hm-api</artifactId> <version>1.0.0</version> </dependency> <!--common--> <dependency> <groupId>com.hmall</groupId> <artifactId>hm-common</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-extension</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--单元测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!--nacos配置管理,包括nacos-client等--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!--读取bootstrap文件--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <!--ES--> <dependency> <groupId>co.elastic.clients</groupId> <artifactId>elasticsearch-java</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>jakarta.json</groupId> <artifactId>jakarta.json-api</artifactId> </dependency> </dependencies> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
拷贝作业提示里面的关键文件,创建三层架构代码
其中factories文件如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.hmall.search.config.EsConfiguration
application.yaml
server: port: 8087 hm: swagger: title: 搜索服务接口文档 package: com.hmall.search.controller
boostrap.yaml文件如下
注意:我这里把所有工程里面的namespace都干掉了,采用的默认public
spring: application: name: search-service # 服务名称 profiles: active: dev cloud: nacos: server-addr: ${NACOS_ADDR:192.168.101.68:8848} # nacos地址 config: #配置中心 file-extension: yaml # 文件后缀名 group: ${GROUP_NAME:DEFAULT_GROUP} # 配置分组 shared-configs: # 共享配置 - dataId: shared-jdbc.yaml # 共享mybatis配置 - dataId: shared-log.yaml # 共享日志配置 - dataId: shared-swagger.yaml # 共享日志配置
- controller
package com.hmall.search.controller; import com.hmall.common.domain.PageDTO; import com.hmall.search.domain.po.ItemDoc; import com.hmall.search.domain.query.ItemPageQuery; import com.hmall.search.service.ISearchService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Api(tags = "搜索相关接口") @RestController @RequestMapping("/search") @RequiredArgsConstructor public class SearchController { private final ISearchService searchService; @ApiOperation("搜索商品") @GetMapping("/list") public PageDTO<ItemDoc> search(ItemPageQuery query) { PageDTO<ItemDoc> search = searchService.search(query); return search; } }
serviceImpl
package com.hmall.search.service.impl; import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.SortOrder; import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; import co.elastic.clients.elasticsearch.core.search.TotalHits; import co.elastic.clients.json.JsonData; import com.alibaba.cloud.commons.lang.StringUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.hmall.common.domain.PageDTO; import com.hmall.search.domain.po.ItemDoc; import com.hmall.search.domain.query.ItemPageQuery; import com.hmall.search.service.ISearchService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Service @Slf4j @AllArgsConstructor public class SearchServiceImpl implements ISearchService { private final ElasticsearchClient esClient; @Override public PageDTO<ItemDoc> search(ItemPageQuery query) { try { SearchRequest.Builder builder = new SearchRequest.Builder(); builder .query(q -> { q.bool(b -> { b.must(b1->{ //如果搜索关键字不为空 if (StringUtils.isNotEmpty(query.getKey())) { b1.multiMatch(m -> m .query(query.getKey()) .fields("name", "brand", "category", "spec")); } else { //查询所有记录 b1.matchAll(m -> m); } return b1; }); if (StringUtils.isNotEmpty(query.getCategory())) { b.filter(f -> f.term(t -> t.field("category").value(query.getCategory()))); } //品牌 if (StringUtils.isNotEmpty(query.getBrand())) { b.filter(f -> f.term(t -> t.field("brand").value(query.getBrand()))); } //价格 if (query.getMinPrice() != null && query.getMaxPrice() != null) { b.filter(f -> f.range(r -> r.field("price").gte(JsonData.of(query.getMinPrice())).lte(JsonData.of(query.getMaxPrice())))); } return b; } ); return q; } ) .size(query.getPageSize()) .from(query.from()); //指定索引 builder.index("items197"); //排序 if (StringUtils.isNotEmpty(query.getSortBy())) { builder.sort(sort -> sort.field(f -> f.field(query.getSortBy()).order(query.getIsAsc() ? SortOrder.Asc : SortOrder.Desc))); } //高亮 builder.highlight(h -> h .fields("name", f -> f .preTags("<em>") .postTags("</em>"))); //构建请求 SearchRequest build = builder.build(); //发送请求 SearchResponse<ItemDoc> response = esClient.search(build, ItemDoc.class); //解析响应 //匹配的总数量 TotalHits total = response.hits().total(); //获取结果列表 List<Hit<ItemDoc>> hits = response.hits().hits(); //转为List<ItemDoc> List<ItemDoc> itemDocs = hits.stream().map(hit -> { ItemDoc source = hit.source(); //解析高亮结果 Map<String, List<String>> highlightFields = hit.highlight(); if (highlightFields != null) { List<String> name = highlightFields.get("name"); if (name != null) { String highlightName = name.get(0); source.setName(highlightName); } } return source; }).collect(Collectors.toList()); Page<ItemDoc> page = Page.of(query.getPageNo(), query.getPageSize(), total.value()); PageDTO<ItemDoc> pageDTO = PageDTO.of(page); pageDTO.setList(itemDocs); return pageDTO; } catch (IOException e) { e.printStackTrace(); } return null; } }
2.修改配置文件
修改gateway文件
追加新的路由信息
完整的如下
spring: cloud: gateway: # default-filters: # - name: FirstFilter routes: - id: item # 路由规则id,自定义,唯一 uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表 predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务 - Path=/items/** # 这里是以请求路径作为判断规则 # - Header=X-Request-Id,\d+ #表示数字 - id: product uri: lb://item-service predicates: - Path=/product/** filters: - StripPrefix=1 - id: productb uri: lb://item-service predicates: - Path=/productb/** - Header=X-Tag, new filters: - StripPrefix=1 - id: cart uri: lb://cart-service predicates: - Path=/carts/** - id: user uri: lb://user-service predicates: - Path=/users/**,/addresses/** - id: trade uri: lb://trade-service predicates: - Path=/orders/** - id: pay uri: lb://pay-service predicates: - Path=/pay-orders/** - id: search uri: lb://search-service predicates: - Path=/search/** hm: jwt: location: classpath:hmall.jks # 秘钥地址 alias: hmall # 秘钥别名 password: hmall123 # 秘钥文件密码 tokenTTL: 30m # 登录有效期 auth: excludePaths: # 无需登录校验的路径 - /search/** - /users/login - /items/** - /productb/**
nacos新增search-service-dev.yaml
hm: es: host: 192.168.101.68 port: 9200
此时配置项一共:13个,如下
如有问题,可以直接替换我的:
nacos_config_export_20250117140343.zip
重启工程
搜索验证
验证完整购物流程也没有问题
3.问题说明
- 个别同学会遇到seata报错,是因为docker里面的seata部署在public命名空间,我们的服务是自创的一个namespace,所以建议大家把namespace都拿掉,或者重新部署docker里面的seata,我采纳了前者
此时就可以使用分布式事务
- 访问搜索服务提示404,直接拷贝我的gateway配置文件,替换自己本地即可
3.代码参考
nacos配置文件
nacos_config_export_20250117140343.zip
完整工程