樂優商場項目day12—搭建搜索微服務(2)

 

一、從spu構建goods

 

@Service
public class SearchService {

    @Autowired
    private BrandClient brandClient;

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpecificationClient specificationClient;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    public Goods buildGoods(Spu spu) throws IOException {
        Goods goods = new Goods();

        //根據分類的id查詢分類名稱
        List<String> names = this.categoryClient.queryNamesByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        //根據品牌id查詢品牌
        Brand brand = this.brandClient.queryBrandById(spu.getBrandId());
        //根據spuId查詢所有的sku
        List<Sku> skus = this.goodsClient.querySkusBySpuId(spu.getId());
        //初始化一個價格集合,收集所有的sku的價格
        List<Long> prices = new ArrayList<>();
        //收集sku的必要字段
        List<Map<String, Object>> skuMapList = new ArrayList<>();
        skus.forEach(sku -> {
            prices.add(sku.getPrice());

            Map<String, Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            map.put("price", sku.getPrice());
            //獲取sku中的圖片,數據庫的圖片可能是多張,多張是以“,”分割,所有也以逗號來切割返回的圖片數組,獲取第一張圖片
            map.put("image", StringUtils.isNotBlank(sku.getImages()) ? StringUtils.split(sku.getImages(), ",")[0] : "");
            skuMapList.add(map);
        });

        // 根據spu中的cid3查詢出所有的搜索規格參數
        List<SpecParam> params = this.specificationClient.queryParams(null, spu.getCid3(), null, true);

        // 查詢spuDetail。獲取規格參數值
        SpuDetail spuDetail = this.goodsClient.querySpuDetailBySpuId(spu.getId());
        //把同樣的規格參數值,進行反序列化
        Map<String, Object> genericSpecMap = MAPPER.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<String, Object>>() {});
        //把特殊的規格參數值,進行反序列化
        Map<String, List<Object>> specialSpecMap = MAPPER.readValue(spuDetail.getSpecialSpec(), new TypeReference<Map<String, List<Object>>>() {});

        // 定義map接收{規格參數名,規格參數值}
        Map<String, Object> specs = new HashMap<>();
        params.forEach(param -> {
            // 判斷是否是通用的規格參數
            if (param.getGeneric()) {
                // 獲取通用規格參數值
                String value = genericSpecMap.get(param.getId().toString()).toString();
                // 判斷是否是數值類型
                if (param.getNumeric()){
                    // 如果是數值的話,判斷該數值落在那個區間
                    value = chooseSegment(value, param);
                }
                // 把參數名和值放入結果集中
                specs.put(param.getName(), value);
            } else {
                //如果是特殊的規格參數,從specialSpecMap中獲取值
                List<Object> value = specialSpecMap.get(param.getId().toString());
                specs.put(param.getName(),value);
            }
        });


        goods.setId(spu.getId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setBrandId(spu.getBrandId());
        goods.setCreateTime(spu.getCreateTime());
        goods.setSubTitle(spu.getSubTitle());
        //拼接all字段,需要分類名稱以及品牌名稱
        goods.setAll(spu.getTitle() + " " + StringUtils.join(names," ") + " " + brand.getName());
        //獲取spu下的所有sku價格
        goods.setPrice(prices);
        //獲取spu下的所有sku,並轉化成json字符串
        goods.setSkus(MAPPER.writeValueAsString(skuMapList));
        //獲取所有查詢的規格參數{name:value}
        goods.setSpecs(specs);

        return goods;
    }


    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;
    }


}

 

二、導入數據

1.創建GoodsRepository 接口,後面可以通過這個接口增刪改查操作數據。

 

2.編寫測試類,導入數據
(因爲數據只需導入一次,所有寫個測試類,導入一下就可以了。)

@SpringBootTest
@RunWith(SpringRunner.class)
public class ElasticsearchTest {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private SearchService searchService;

    @Autowired
    private GoodsClient goodsClient;


    @Test
    public void test(){

        // 創建索引
        this.elasticsearchTemplate.createIndex(Goods.class);
        // 配置映射
        this.elasticsearchTemplate.putMapping(Goods.class);
        Integer page = 1;
        Integer rows = 100;

        do {
            // 分批查詢spuBo,獲取分頁結果集
            PageResult<SpuBo> pageResult = this.goodsClient.querySpuBoByPage(null, true, page, rows);
            //獲取當前頁數據
            List<SpuBo> items = pageResult.getItems();
            // 遍歷spubo集合轉化爲List<Goods>
            List<Goods> goodsList =items.stream().map(spuBo -> {
                try {
                    return this.searchService.buildGoods((Spu) spuBo);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }).collect(Collectors.toList());
            this.goodsRepository.saveAll(goodsList);

            // 獲取當前頁的數據條數,如果是最後一頁,沒有100條
            rows = pageResult.getItems().size();
            // 每次循環頁碼加1
            page++;
        } while (rows == 100);

    }

}

運行這個測試類,就可以將數據導入了。

在Kibana裏查看一下:

數據導入成功了。

 

 

三、完成基本搜素

1.發起異步請求。

在頁面加載後,就展示出搜索結果。我們應該在頁面加載時,獲取地址欄請求參數,併發起異步請求,查詢後臺數據,然後在頁面渲染。

在data中定義一個對象,記錄請求的參數;
然後通過鉤子函數created,在頁面加載時獲取請求參數,並記錄下來;
然後發起請求,搜索數據。

 

在leyou-gateway中的CORS配置類中,添加允許信任域名:

並在leyou-gateway工程的Application.yml中添加網關映射:

 

重啓網關,然後去頁面看一下:

 

2.創建一個對象來接收請求的json數據:

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

    private Integer page;// 當前頁

    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;
    }
}

 

3.controller

@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);
    }
}

 

4.service

    public PageResult<Goods> search(SearchRequest request) {
        String key = request.getKey();
        // 判斷是否有搜索條件,如果沒有,直接返回null。不允許搜索全部商品
        if (StringUtils.isBlank(key)) {
            return null;
        }

        // 構建查詢條件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        // 1、對key進行全文檢索查詢
        queryBuilder.withQuery(QueryBuilders.matchQuery("all", key).operator(Operator.AND));

        // 2、通過sourceFilter設置返回的結果字段,我們只需要id、skus、subTitle
        queryBuilder.withSourceFilter(new FetchSourceFilter(
                new String[]{"id","skus","subTitle"}, null));

        // 3、分頁
        // 準備分頁參數
        int page = request.getPage();
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page - 1, size));

        // 4、查詢,獲取結果
        Page<Goods> goodsPage = this.goodsRepository.search(queryBuilder.build());

        // 封裝結果並返回
        return new PageResult<>(goodsPage.getTotalElements(), goodsPage.getTotalPages(), goodsPage.getContent());
    }

 

啓動leyou-search服務,試一下:

 

基本搜索就完成了。

 

 

 

 

 

 

發佈了91 篇原創文章 · 獲贊 35 · 訪問量 6821
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章