乐优商城(六)基本搜索

1. 搭建搜索微服务

在门户系统中,用户一般会直接搜索自己想要的商品,所以我们需要一个搜索微服务。

面对复杂的搜索业务和数据量,使用传统数据库搜索就会显得力不从心,我们需要搜索的效率更高的全文检索技术——Elasticsearch。

1.1 创建工程

  1. 右键 leyou 项目 --> New Module --> Maven --> Next

  2. 填写项目信息 --> Next

    在这里插入图片描述

  3. 填写保存的位置 --> Finish

    在这里插入图片描述

  4. 添加依赖

    <?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">
        <parent>
            <artifactId>leyou</artifactId>
            <groupId>com.leyou.parent</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.leyou.search</groupId>
        <artifactId>leyou-search</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencies>
            <!--web-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- elasticsearch -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            </dependency>
            <!--eureka client-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <!--feign-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
            <!--leyou-common-->
            <dependency>
                <groupId>com.leyou.common</groupId>
                <artifactId>leyou-common</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <!--leyou-item-interface-->
            <dependency>
                <groupId>com.leyou.item</groupId>
                <artifactId>leyou-item-interface</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <!--test-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
            </dependency>
        </dependencies>
    </project>
    
  5. 编写配置文件 application.yaml

    server:
      port: 8083
    spring:
      application:
        name: search-service
      data:
        elasticsearch:
          cluster-name: elasticsearch
          cluster-nodes: 192.168.222.132:9300
    
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:10086/eureka
      instance:
        lease-renewal-interval-in-seconds: 5
        lease-expiration-duration-in-seconds: 10
    
  6. 编写启动类

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    public class LeyouSearchApplication {
        public static void main(String[] args) {
            SpringApplication.run(LeyouSearchApplication.class,args);
        }
    }
    
    

1.2 分析索引库数据格式

接下来我们需要将 MySQL 的商品数据导入 Elasticsearch 的索引库,以便用户搜索。

但索引库的数据格式应该是什么样的呢?我们先来看一下用户搜索后的结果。

在这里插入图片描述

另外,页面还有过滤条件

在这里插入图片描述

从上面搜索的结果可以分析出,我们需要的数据格式有:

  • spuId
  • skuId
  • 商品分类 id
  • 品牌 id
  • 图片
  • 价格
  • 商品的创建时间
  • SKU 信息集
  • 可搜索的规格参数

1.3 创建实体类

根据上面分析的数据格式,创建实体类 Goods,我们可以根据这个实体类来创建索引库和映射。

@Document(indexName = "goods", type = "docs", shards = 1, replicas = 0)
public class Goods {
    @Id
    private Long id; // spuId
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String all; // 所有需要被搜索的信息,包含标题,分类,品牌
    @Field(type = FieldType.Keyword, index = false)
    private String subTitle;// 卖点
    private Long brandId;// 品牌id
    private Long cid1;// 1级分类id
    private Long cid2;// 2级分类id
    private Long cid3;// 3级分类id
    private Date createTime;// 创建时间
    private List<Long> price;// 价格
    @Field(type = FieldType.Keyword, index = false)
    private String skus;// List<sku>信息的json结构
    private Map<String, Object> specs;// 可搜索的规格参数,key是参数名,值是参数值
}

特殊属性解释:

  • all:用来进行全文检索的字段,里面包含标题、商品分类信息,
  • price:价格数组,是所有 SKU 的价格集合。
  • skus:用于页面展示的 SKU 信息,不索引,不搜索。包含 skuId、image、price、title 字段

1.4 调用商品微服务接口

索引库中的数据来自于数据库,我们不能直接去查询商品的数据库,因为真实开发中,每个微服务都是相互独立的,包括数据库也是一样。所以我们只能调用商品微服务提供的接口服务

先思考我们需要的数据:

  • SPU 信息
  • SKU 信息
  • SPU 详情
  • 商品分类名称(拼接 all 字段)
  • 品牌名称
  • 规格参数

再思考我们需要哪些接口:

  • 第一:分页查询 SPU 的接口,已经写过。
  • 第二:根据 spuId 查询 SKU 的接口,已经写过。
  • 第三:根据 spuId 查询 SpuDetail 的接口,已经写过。
  • 第四:根据商品分类 id,查询商品分类名称的接口,没写过。
  • 第五:根据商品品牌 id,查询商品品牌的接口,没写过。
  • 第六:根据商品分类 id,查询可搜索的规格参数接口,已经写过。

1.4.1 提供查询商品分类名称接口

在 CategoryController 中添加 queryNamesByIds 方法

/**
 * 根据商品分类 id,查询商品分类名称
 * @param ids
 * @return
 */
@GetMapping("/names")
public ResponseEntity<List<String>> queryNamesByIds(@RequestParam("ids") List<Long> ids) {
    List<String> names = categoryService.queryNamesByIds(ids);
    if(CollectionUtils.isEmpty(names)) {
        return ResponseEntity.notFound().build(); // 响应 404
    }
    return ResponseEntity.ok(names);
}

1.4.2 提供查询商品品牌名称接口

  1. 在 BrandController 中添加 queryBrandById 方法

    /**
     * 根据品牌 id 查询品牌
     * @param id
     * @return
     */
    @GetMapping("{id}")
    public ResponseEntity<Brand> queryBrandById(@PathVariable("id") Long id) {
        Brand brand = brandService.queryBrandById(id);
        if (brand == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(brand);
    }
    
  2. 在 BrandService 中添加 queryBrandById 方法

    /**
     * 根据品牌 id 查询品牌
     * @param id
     * @return
     */
    public Brand queryBrandById(Long id) {
        Brand brand = brandMapper.selectByPrimaryKey(id);
        return brand;
    }
    

1.4.3 调用商品微服务接口

我们接下来需要在搜索微服务中调用商品微服务的接口,我们使用 Feign 来远程调用服务。

  1. 在 leyou-item-interface 中,提供对外的 API 接口

    在这里插入图片描述

    @RequestMapping("/spu")
    public interface SpuApi {
        /**
         * 根据查询条件分页查询商品信息
         * @param key 搜索条件
         * @param saleable 上下架
         * @param page 当前页
         * @param rows 每页大小
         * @return
         */
        @GetMapping("/page")
        public PageResult<SpuBo> querySpuByPage(
                @RequestParam(name = "key", required = false) String key,
                @RequestParam(name = "saleable", required = false) Boolean saleable,
                @RequestParam(name = "page", defaultValue = "1") Integer page,
                @RequestParam(name = "rows", defaultValue = "5") Integer rows
        );
    
        /**
         * 通过 spuId 查询 SpuDetail
         * @param spuId
         * @return
         */
        @GetMapping("/detail/{spuId}")
        public SpuDetail querySpuDetailBySpuId(@PathVariable("spuId") Long spuId);
    
        /**
         * 通过 spuId 查询 Sku 集合
         * @param spuId
         * @return
         */
        @GetMapping("/sku/list")
        public List<Sku> querySkusBySpuId(@RequestParam("id") Long spuId);
    }
    
    
    @RequestMapping("/category")
    public interface CategoryApi {
        /**
         * 根据商品分类 id,查询商品分类名称
         * @param ids
         * @return
         */
        @GetMapping("/names")
        public List<String> queryNamesByIds(@RequestParam("ids") List<Long> ids);
    }
    
    @RequestMapping("/brand")
    public interface BrandApi {
        /**
         * 根据品牌 id 查询品牌
         * @param id
         * @return
         */
        @GetMapping("{id}")
        public Brand queryBrandById(@PathVariable("id") Long id);
    }
    
    @RequestMapping("/spec")
    public interface SpecificationApi {
        /**
         * 根据条件查询规格参数
         *
         * @param gid
         * @return
         */
        @GetMapping("/params")
        public List<SpecParam> querySpecParams(
                @RequestParam(value = "gid", required = false) Long gid,
                @RequestParam(value = "cid", required = false) Long cid,
                @RequestParam(value = "generic", required = false) Boolean generic,
                @RequestParam(value = "searching", required = false) Boolean searching
        );
    }
    
  2. 在服务调用方 leyou-search 中编写 FeignClient,直接继承 leyou-item-interface 提供的对外接口

    在这里插入图片描述

    @FeignClient(value = "item-service")
    public interface BrandClient extends BrandApi {
    }
    
    @FeignClient(value = "item-service")
    public interface SpuClient extends SpuApi {
    }
    
    @FeignClient(value = "item-service")
    public interface CategoryClient extends CategoryApi {
    }
    
    @FeignClient(value = "item-service")
    public interface SpecificationClient extends SpecificationApi {
    }
    

1.4.4 测试

  1. 写一个 CategoryClient 的测试类

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = LeyouSearchApplication.class)
    public class CategoryClientTest {
        @Autowired
        private CategoryClient categoryClient;
    
        @Test
        public void testQueryNamesByIds() {
            List<String> names = categoryClient.queryNamesByIds(Arrays.asList(1L, 2L, 3L));
            for (String name : names) {
                System.out.println(name);
            }
        }
    }
    
  2. 运行结果

    图书、音像、电子书刊
    电子书刊
    电子书
    

1.5 创建索引库和映射

  1. 创建 GoodsRepository

    在这里插入图片描述

    public interface GoodsRepository extends ElasticsearchRepository<Goods, Long> {
    }
    
  2. 创建一个测试类,在里面进行数据的操作

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = LeyouSearchApplication.class)
    public class ElasticsearchTest {
    
        @Autowired
        private GoodsRepository goodsRepository;
    
        @Autowired
        private ElasticsearchTemplate template;
    
        @Test
        public void createIndex(){
            // 创建索引库,以及映射
            this.template.createIndex(Goods.class);
            this.template.putMapping(Goods.class);
        }
    }
    
  3. 打开 kibana 查看映射,创建成功

    在这里插入图片描述

1.6 导入商品数据

导入数据其实就是把查询到的 Spu 转变为 Goods 来保存。

  1. 编写一个 SearchService,创建 buildGoods 方法,把 Spu 转变为 Goods

    @Service
    public class SearchService {
        @Autowired
        private BrandClient brandClient;
        @Autowired
        private CategoryClient categoryClient;
        @Autowired
        private SpuClient spuClient;
        @Autowired
        private SpecificationClient specificationClient;
    
        // Jackson 工具
        private static final ObjectMapper MAPPER = new ObjectMapper();
    
        /**
         * 把 Spu 转变为 Goods
         *
         * @param spu
         * @return
         * @throws IOException
         */
        public Goods buildGoods(Spu spu) throws IOException {
            // 创建 Goods 对象
            Goods goods = new Goods();
    
            // 根据品牌 id 查询品牌
            Brand brand = this.brandClient.queryBrandById(spu.getBrandId());
    
            // 根据商品分类 id,查询商品分类名称
            List<String> names = this.categoryClient.queryNamesByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
    
            // 查询 spu 下的所有 sku
            List<Sku> skus = this.spuClient.querySkusBySpuId(spu.getId());
            // 创建一个价格集合,用于存放所有 sku 价格
            List<Long> prices = new ArrayList<>();
            // 收集 sku 必要信息的集合
            List<Map<String, Object>> skuMapList = new ArrayList<>();
            // 遍历 skus
            skus.forEach(sku -> {
                prices.add(sku.getPrice());
                Map<String, Object> skuMap = new HashMap<>();
                skuMap.put("id", sku.getId());
                skuMap.put("title", sku.getTitle());
                skuMap.put("price", sku.getPrice());
                // 没有图片时,返回空字符串;有图片时,返回第一张图片地址
                skuMap.put("image", StringUtils.isNotBlank(sku.getImages()) ? StringUtils.split(sku.getImages(), ",")[0] : "");
                skuMapList.add(skuMap);
            });
    
            // 查询出所有的可搜索规格参数
            List<SpecParam> params = this.specificationClient.querySpecParams(null, spu.getCid3(), null, true);
            // 通过 spuId 查询 SpuDetail
            SpuDetail spuDetail = this.spuClient.querySpuDetailBySpuId(spu.getId());
            // 获取通用的规格参数
            Map<Long, Object> genericSpecMap = MAPPER.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<Long, Object>>() {
            });
            // 获取特殊的规格参数
            Map<Long, List<Object>> specialSpecMap = MAPPER.readValue(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<Object>>>() {
            });
            // 定义 Map 接收 {规格参数名,规格参数值}
            Map<String, Object> paramMap = new HashMap<>();
            params.forEach(param -> {
                // 判断是否是通用规格参数
                if (param.getGeneric()) {
                    // 获取通用规格参数值
                    String value = genericSpecMap.get(param.getId()).toString();
                    // 判断是否是数值类型
                    if (param.getNumeric()) {
                        // 如果是数值的话,返回该数值落在的区间
                        value = chooseSegment(value, param);
                    }
                    // 把参数名和值放入结果集中
                    paramMap.put(param.getName(), value);
                } else {
                    // 把参数名和值放入结果集中
                    paramMap.put(param.getName(), specialSpecMap.get(param.getId()));
                }
            });
    
            // 设置参数
            goods.setId(spu.getId());
            goods.setCid1(spu.getCid1());
            goods.setCid2(spu.getCid2());
            goods.setCid3(spu.getCid3());
            goods.setBrandId(spu.getBrandId());
            goods.setCreateTime(spu.getCreateTime());
            goods.setSubTitle(spu.getSubTitle());
            // 拼接 all 字段,包括标题、品牌名称、分类名称
            goods.setAll(spu.getTitle() + " " + brand.getName() + " " + StringUtils.join(names, " "));
            // 获取 spu 下所有 sku 的价格
            goods.setPrice(prices);
            // 获取 spu 下所有 sku,并转化成 json 字符串
            goods.setSkus(MAPPER.writeValueAsString(skuMapList));
            // 获取所有可搜索的规格参数
            goods.setSpecs(paramMap);
    
            return goods;
        }
    
        /**
         * 返回该数值落在的区间
         * @param value
         * @param p
         * @return
         */
        private String chooseSegment(String value, SpecParam p) {
            double val = NumberUtils.toDouble(value);
            String result = "其它";
            // 保存数值段
            for (String segment : p.getSegments().split(",")) {
                String[] segs = segment.split("-");
                // 获取数值范围
                double begin = NumberUtils.toDouble(segs[0]);
                double end = Double.MAX_VALUE;
                if (segs.length == 2) {
                    end = NumberUtils.toDouble(segs[1]);
                }
                // 判断是否在范围内
                if (val >= begin && val < end) {
                    if (segs.length == 1) {
                        result = segs[0] + p.getUnit() + "以上";
                    } else if (begin == 0) {
                        result = segs[1] + p.getUnit() + "以下";
                    } else {
                        result = segment + p.getUnit();
                    }
                    break;
                }
            }
            return result;
        }
    }
    
  2. 编写一个测试方法,先分页查询 Spu,然后将 Spu 转化成 Goods,最后导入 Goods 数据。循环以上过程,直到分页为最后一页。

    @Test
    public void saveGoods() {
        Integer page = 1;
        Integer rows = 100;
    
        do {
            // 分页查询 Spu
            PageResult<SpuBo> pageResult = this.spuClient.querySpuByPage(null, null, page, rows);
    
            // 遍历 Spu 集合转化为 List<Goods>
            List<Goods> goodsList = pageResult.getItems().stream().map(spuBo -> {
                try {
                    return this.searchService.buildGoods((Spu) spuBo);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }).collect(Collectors.toList());
    
            // 导入数据
            this.goodsRepository.saveAll(goodsList);
    
            // 获取当前页的数据条数
            rows = pageResult.getItems().size();
            // 每次循环页码加一
            page++;
        } while (rows == 100);
    }
    
  3. 打开 kibana 查询, 导入数据成功,共 183 条

    在这里插入图片描述

2. 实现基本搜索

2.1 分析页面

  1. 在首页的顶部有一个搜索框,我们输入内容,点击搜索

    在这里插入图片描述

  2. 就会跳转到搜索页 search.html,并且将搜索关键字以请求参数携带过来

    在这里插入图片描述

2.2 发送异步请求

我们希望在 search.html 页面加载后,就展示出搜索结果。

具体做法就是在页面加载时,获取地址栏请求参数,并发起异步请求,查询后台数据,然后在页面渲染。

  1. 打开 leyou-portal 工程中的 search.html,找到提前定义好的 Vue 实例

    <script type="text/javascript">
        var vm = new Vue({
            el: "#searchApp",
            data: {
            },
            components:{
                // 加载页面顶部组件
                lyTop: () => import("./js/pages/top.js")
            }
        });
    </script>
    
  2. 我们在 data 中定义一个对象,记录请求的参数

    data: {
        search:{
            key:"", // 搜索页面的关键字
        }
    }
    
  3. 我们通过 created 函数,在页面加载时获取请求参数,并按请求条件搜索

    created(){
        // 判断是否有请求参数
        if(!location.search){
            return;
        }
        // 将请求参数转为对象
        const search = ly.parse(location.search.substring(1));
        // 记录在data的search对象中
        this.search = search;
    
        // 发起请求,根据条件搜索
        this.loadData();
    }
    
  4. 发起异步请求,搜索数据

    methods: {
        loadData(){
            ly.http.post("/search/page", this.search).then(resp=>{
                console.log(resp);
            });
        }
    }
    

2.3 后台提供搜索接口

2.3.1 添加网关映射

在 leyou-gateway 工程的 Application.yaml 中添加网关映射

zuul:
  prefix: /api
  routes:
    item-service: /item/**
    search-service: /search/**

2.3.2 添加允许跨域

在 leyou-gateway 中的 CORS 配置类中,添加允许信任域名

@Configuration
public class LeyouCorsConfigration {

    @Bean
    public CorsFilter corsFilter() {
        // 初始化 cors 配置对象
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true); //是否发送 Cookie 信息
        config.addAllowedOrigin("http://manage.leyou.com"); //允许的域
        config.addAllowedOrigin("http://www.leyou.com"); //允许的域
        config.addAllowedMethod("*"); //允许的请求方式
        config.addAllowedHeader("*"); //允许的头信息

        //初始化 cors 配置源对象
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config); //添加映射路径,拦截一切请求

        return new CorsFilter(configSource); //返回 CorsFilter
    }
}

2.3.3 实体类

在 leyou-search 工程 pojo 包中创建实体类 SearchRequest,用来接受搜索条件

public class SearchRequest {
    private String key;// 搜索条件

    private Integer page;// 当前页

    private String sortBy; // 排序字段

    private Boolean descending; // 是否降序

    private static final Integer DEFAULT_SIZE = 20;// 每页大小,不从页面接收,而是固定大小
    private static final Integer DEFAULT_PAGE = 1;// 默认页

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public Integer getPage() {
        if(page == null){
            return DEFAULT_PAGE;
        }
        // 获取页码时做一些校验,不能小于1
        return Math.max(DEFAULT_PAGE, page);
    }

    public void setPage(Integer page) {
        this.page = page;
    }

    public Integer getSize() {
        return DEFAULT_SIZE;
    }

    public String getSortBy() {
        return sortBy;
    }

    public void setSortBy(String sortBy) {
        this.sortBy = sortBy;
    }

    public Boolean getDescending() {
        return descending;
    }

    public void setDescending(Boolean descending) {
        this.descending = descending;
    }
}

2.3.4 Controller

在这里插入图片描述

分析前端页面发送的请求,可以得知:

  • 请求方式:POST
  • 请求路径:/search/page
  • 请求参数:JSON 格式,目前只有一个属性:key 搜索关键字,但是搜索结果页一定是带有分页查询的,所以将来肯定会有 page 属性。
  • 返回结果:作为分页结果,我们可以使用之前定义的 PageResult 类

在 leyou-search 工程 controller 包中创建 SearchController

@RestController
@RequestMapping
public class SearchController {

    @Autowired
    private SearchService searchService;

    /**
     * 搜索商品
     *
     * @param request
     * @return
     */
    @PostMapping("/page")
    public ResponseEntity<PageResult<Goods>> search(@RequestBody SearchRequest request) {
        PageResult<Goods> result = this.searchService.search(request);
        if (result == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        return ResponseEntity.ok(result);
    }
}

2.3.5 Service

在 SearchService 中添加方法 search

/**
 * 根据搜索条件搜索数据
 *
 * @param request
 * @return
 */
public PageResult<Goods> search(SearchRequest request) {
    // 获取搜索条件
    String key = request.getKey();

    // 判断是否有搜索条件,如果没有,直接返回 null。不允许搜索全部商品
    if (StringUtils.isBlank(key)) {
        return null;
    }

    // 构建查询条件
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

    // 对 key 进行匹配查询
    queryBuilder.withQuery(QueryBuilders.matchQuery("all", key).operator(Operator.AND));

    // 通过 sourceFilter 设置返回的结果字段,我们只需要 id、skus、subTitle
    queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "skus", "subTitle"}, null));

    // 分页
    int page = request.getPage();
    int size = request.getSize();
    queryBuilder.withPageable(PageRequest.of(page - 1, size));
    
    // 加入请求中的排序条件
    String sortBy = request.getSortBy();
    Boolean descending = request.getDescending();
    if (StringUtils.isNotBlank(sortBy)) {
        queryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(descending ? SortOrder.DESC : SortOrder.ASC));
    }

    // 执行查询
    Page<Goods> goodsPage = this.goodsRepository.search(queryBuilder.build());

    // 封装结果并返回
    return new PageResult<Goods>(goodsPage.getTotalElements(), goodsPage.getContent(), goodsPage.getTotalPages());
}

2.3.6 测试

  1. 重启 leyou-search 工程

  2. 刷新页面,请求成功

    在这里插入图片描述

  3. 打开控制台,查看数据。数据是得到了,但由于我们查询结果只保留了三个字段,导致有很多字段都是 null。

    在这里插入图片描述

  4. 解决办法很简单,在 leyou-search 的 application.yaml 中添加一行配置, json 处理时忽略空值。

    spring:
      jackson:
        default-property-inclusion: non_null # 配置json处理时忽略空值
    

    在这里插入图片描述

2.4 页面渲染

略,交给前端吧

2.5 测试

再次搜索手机,可以看到页面已经展示出搜索结果了,但除了我们之前自己新增的商品,其他商品图片都无法显示。这是因为我们之前忘了将这些商品的图片导入 FastDFS,接下就导入图片信息。

在这里插入图片描述

2.6 导入图片信息

  1. 在 leyou 下创建 static 目录下,并将 image.zip 上传到该目录下

    在这里插入图片描述

  2. 使用命令解压缩

    unzip images.zip
    
  3. 删除压缩包

    rm -f images.zip
    
  4. 修改 nginx 配置,使 nginx 反向代理这些图片地址

    vim opt/nginx/conf/nginx.conf
    
  5. 修改成如下配置

    server {
        listen       80;
        server_name  image.leyou.com;
    
        location ~/group([0-9])/ {
            ngx_fastdfs_module;
        }
    
        location / {
            root   /leyou/static;
        }
    
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
    
  6. 重新加载 nginx 配置

    nginx -s reload
    
  7. 重启 leyou-search 工程

2.7 再次测试

再次搜索手机,成功显示图片

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bm1998

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值