作业参考

简介: 本文介绍了基于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

相关文章
|
4月前
|
负载均衡 应用服务中间件 Nacos
Nacos配置中心
本文详细讲解了Nacos作为配置中心的核心功能与实践应用,涵盖配置管理、热更新、共享配置及优先级规则,并通过搭建Nacos集群实现高可用部署,帮助开发者掌握微服务环境下配置的集中化管理方案。
 Nacos配置中心
|
4月前
|
NoSQL Linux 网络安全
Redis集群部署指南
本章基于CentOS7搭建Redis集群,涵盖单机安装、主从复制、哨兵高可用及分片集群配置,详细演示Redis集群部署与管理全过程。
Redis集群部署指南
|
4月前
|
负载均衡 Java 数据安全/隐私保护
Gateway服务网关
本文介绍了微服务架构中网关的核心作用,包括请求路由、权限控制和限流等功能,重点讲解了Spring Cloud Gateway的使用方法。内容涵盖网关的快速搭建、路由配置、断言与过滤器工厂的应用,并通过实际案例演示了如何实现全局过滤器与跨域问题的解决方案,帮助开发者掌握微服务统一入口的关键技术。
 Gateway服务网关
|
4月前
|
SpringCloudAlibaba Java Nacos
SpringCloud概述
Spring Cloud是微服务架构的综合解决方案,由Spring团队推出,具备约定大于配置、组件丰富、开箱即用等特点。为解决Netflix组件停更问题,阿里推出Spring Cloud Alibaba,集成Nacos、Sentinel、Seata等高性能中间件,成为主流技术栈选择。
|
4月前
|
存储 SQL Java
Spring Boot使用slf4j进行日志记录
本文介绍了在Spring Boot项目中使用SLF4J结合Logback进行日志管理的最佳实践。相比直接使用System.out.println(),SLF4J作为日志门面,解耦日志实现,提升可维护性。通过application.yml配置日志级别与logback.xml定义输出格式、路径、滚动策略等,实现灵活高效的日志记录。推荐遵循阿里巴巴开发规范,统一使用SLF4J API,便于日志系统替换与统一管理。
|
3月前
|
人工智能 算法 BI
企业 GEO 效果评估指标体系
本文构建了企业GEO优化效果评估指标体系,涵盖AI引用率、首条占位率、线索转化率等核心指标,结合收录率、关键词覆盖量等辅助指标,建立分层级监测模型,并配套工具与周期建议,助力企业科学评估并优化AI搜索表现。
|
敏捷开发 数据可视化 搜索推荐
游戏开发团队必备!哪些办公软件能像板栗看板提升节日协作效率?
本文深入剖析了6款可视化团队协作办公软件在游戏行业的节日协作中的应用,包括板栗看板、Trello、Asana、飞书、Jira和Monday.com。这些工具通过任务可视化、团队协同、项目规划、即时通讯、工作流自动化等功能,助力游戏公司在节日期间高效协作,确保项目按时上线,提升游戏品质,最终为玩家带来精彩绝伦的游戏体验。
261 3
|
Windows
显示器设置
显示器设置
458 2
|
数据采集 安全 网络安全
提高企业进入国外市场的“免疫力”——阿里云CDN安全能力分析
提高企业进入国外市场的“免疫力”——阿里云CDN安全能力分析
|
Java 开发者
JAVA多线程初学者必看:为何选择继承Thread还是Runnable,这其中有何玄机?
【6月更文挑战第19天】在Java中创建线程,可选择继承Thread类或实现Runnable接口。继承Thread直接运行,但限制了多重继承;实现Runnable更灵活,允许多线程共享资源且利于代码组织。推荐实现Runnable接口,以保持类的继承灵活性和更好的资源管理。
333 2

热门文章

最新文章