乐优商场项目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服务,试一下:
基本搜索就完成了。