作业参考

简介: 本文介绍了基于SpringBoot的搜索服务开发与数据批量导入方案。通过整合Elasticsearch,实现商品数据的批量同步与高效搜索功能,并利用Nacos进行配置管理,Gateway配置路由,完成微服务架构下的完整购物流程验证,提供了一套可落地的实践参考。(238字)

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

完整工程

hmall-micro.zip

相关文章
|
12天前
|
数据采集 人工智能 安全
|
8天前
|
编解码 人工智能 自然语言处理
⚽阿里云百炼通义万相 2.6 视频生成玩法手册
通义万相Wan 2.6是全球首个支持角色扮演的AI视频生成模型,可基于参考视频形象与音色生成多角色合拍、多镜头叙事的15秒长视频,实现声画同步、智能分镜,适用于影视创作、营销展示等场景。
627 4
|
8天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化
随机森林是一种基于决策树的集成学习算法,通过构建多棵决策树并结合它们的预测结果来提高准确性和稳定性。其核心思想包括两个随机性:Bootstrap采样(每棵树使用不同的训练子集)和特征随机选择(每棵树分裂时只考虑部分特征)。这种方法能有效处理大规模高维数据,避免过拟合,并评估特征重要性。随机森林的超参数如树的数量、最大深度等可通过网格搜索优化。该算法兼具强大预测能力和工程化优势,是机器学习中的常用基础模型。
346 164
|
7天前
|
机器学习/深度学习 自然语言处理 机器人
阿里云百炼大模型赋能|打造企业级电话智能体与智能呼叫中心完整方案
畅信达基于阿里云百炼大模型推出MVB2000V5智能呼叫中心方案,融合LLM与MRCP+WebSocket技术,实现语音识别率超95%、低延迟交互。通过电话智能体与座席助手协同,自动化处理80%咨询,降本增效显著,适配金融、电商、医疗等多行业场景。
356 155

热门文章

最新文章