master:PC首页搜索查询;

pull/1121/head
liujiang 2025-04-22 20:30:57 +08:00
parent 12b8df9b3a
commit 111f5afe3a
6 changed files with 401 additions and 0 deletions

View File

@ -0,0 +1,56 @@
package com.ruoyi.web.controller.xkt;
import cn.hutool.core.bean.BeanUtil;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.XktBaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.Page;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.web.controller.xkt.vo.storeCustomer.StoreCusGeneralSaleVO;
import com.ruoyi.web.controller.xkt.vo.storeSale.StoreSalePageVO;
import com.ruoyi.web.controller.xkt.vo.storeSale.StoreSalePayStatusVO;
import com.ruoyi.web.controller.xkt.vo.storeSale.StoreSaleVO;
import com.ruoyi.web.controller.xkt.website.IndexSearchVO;
import com.ruoyi.xkt.dto.es.ESProductDTO;
import com.ruoyi.xkt.dto.storeSale.StoreSaleDTO;
import com.ruoyi.xkt.dto.storeSale.StoreSalePageDTO;
import com.ruoyi.xkt.dto.storeSale.StoreSalePageResDTO;
import com.ruoyi.xkt.dto.storeSale.StoreSalePayStatusDTO;
import com.ruoyi.xkt.dto.website.IndexSearchDTO;
import com.ruoyi.xkt.service.IIndexSearchService;
import com.ruoyi.xkt.service.IStoreSaleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
/**
* Controller
*
* @author ruoyi
* @date 2025-03-26
*/
@Api(tags = "网站首页")
@RestController
@RequiredArgsConstructor
@RequestMapping("/rest/v1/website")
public class WebsiteController extends XktBaseController {
final IIndexSearchService searchService;
/**
*
*/
@ApiOperation(value = "网站首页搜索", httpMethod = "POST", response = R.class)
@PostMapping("/index/search")
public R<Page<ESProductDTO>> page(@Validated @RequestBody IndexSearchVO searchVO) throws IOException {
return R.ok(searchService.search(BeanUtil.toBean(searchVO, IndexSearchDTO.class)));
}
}

View File

@ -0,0 +1,38 @@
package com.ruoyi.web.controller.xkt.website;
import com.ruoyi.web.controller.xkt.vo.BasePageVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* @author liujiang
* @version v1.0
* @date 2025/3/27 15:12
*/
@EqualsAndHashCode(callSuper = true)
@ApiModel("网站首页搜索")
@Data
public class IndexSearchVO extends BasePageVO {
@ApiModelProperty(value = "搜索内容")
private String search;
@ApiModelProperty(value = "一级类目ID列表")
private List<String> parCateIdList;
@ApiModelProperty(value = "二级类目ID列表")
private List<String> prodCateIdList;
@ApiModelProperty(value = "最小价格")
private String minPrice;
@ApiModelProperty(value = "最大价格")
private String maxPrice;
@ApiModelProperty(value = "风格列表")
private List<String> styleList;
@ApiModelProperty(value = "季节列表")
private List<String> seasonList;
@ApiModelProperty(value = "排序")
private String sort;
}

View File

@ -0,0 +1,55 @@
package com.ruoyi.xkt.dto.es;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @author liujiang
* @version v1.0
* @date 2025/3/27 15:12
*/
@ApiModel("ES返回商品数据")
@Data
public class ESProductDTO {
@ApiModelProperty(value = "货号")
private String prodArtNum;
@ApiModelProperty(value = "档口商品分类ID")
private String prodCateId;
@ApiModelProperty(value = "档口商品分类名称")
private String prodCateName;
@ApiModelProperty(value = "销量权重")
private String saleWeight;
@ApiModelProperty(value = "推荐权重")
private String recommendWeight;
@ApiModelProperty(value = "人气权重")
private String popularityWeight;
@ApiModelProperty(value = "创建时间")
private String createTime;
@ApiModelProperty(value = "主图")
private String mainPic;
@ApiModelProperty(value = "上级分类名称")
private String parCateName;
@ApiModelProperty(value = "上级分类ID")
private String parCateId;
@ApiModelProperty(value = "单价")
private String prodPrice;
@ApiModelProperty(value = "适合季节")
private String season;
@ApiModelProperty(value = "商品状态")
private String status;
@ApiModelProperty(value = "档口ID")
private String storeId;
@ApiModelProperty(value = "档口名称")
private String storeName;
@ApiModelProperty(value = "风格")
private String style;
@ApiModelProperty(value = "标签")
private List<String> tags;
@ApiModelProperty(value = "标题")
private String prodTitle;
}

View File

@ -0,0 +1,39 @@
package com.ruoyi.xkt.dto.website;
import com.ruoyi.xkt.dto.BasePageDTO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.List;
/**
* @author liujiang
* @version v1.0
* @date 2025/3/27 15:12
*/
@EqualsAndHashCode(callSuper = true)
@ApiModel("网站首页搜索")
@Data
public class IndexSearchDTO extends BasePageDTO {
@ApiModelProperty(value = "搜索内容")
private String search;
@ApiModelProperty(value = "一级类目ID列表")
private List<String> parCateIdList;
@ApiModelProperty(value = "二级类目ID列表")
private List<String> prodCateIdList;
@ApiModelProperty(value = "最小价格")
private String minPrice;
@ApiModelProperty(value = "最大价格")
private String maxPrice;
@ApiModelProperty(value = "风格列表")
private List<String> styleList;
@ApiModelProperty(value = "季节列表")
private List<String> seasonList;
@ApiModelProperty(value = "排序")
private String sort;
}

View File

@ -0,0 +1,24 @@
package com.ruoyi.xkt.service;
import com.ruoyi.common.core.page.Page;
import com.ruoyi.xkt.dto.es.ESProductDTO;
import com.ruoyi.xkt.dto.website.IndexSearchDTO;
import java.io.IOException;
/**
* Service
*
* @author ruoyi
* @date 2025-03-26
*/
public interface IIndexSearchService {
/**
*
*
* @param searchDTO
*/
Page<ESProductDTO> search(IndexSearchDTO searchDTO) throws IOException;
}

View File

@ -0,0 +1,189 @@
package com.ruoyi.xkt.service.impl;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.github.pagehelper.PageInfo;
import com.ruoyi.common.core.page.Page;
import com.ruoyi.framework.es.EsClientWrapper;
import com.ruoyi.xkt.dto.es.ESProductDTO;
import com.ruoyi.xkt.dto.website.IndexSearchDTO;
import com.ruoyi.xkt.service.IIndexSearchService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.util.stream.Collectors;
/**
*
*
* @author ruoyi
* @date 2025-03-26
*/
@Service
@RequiredArgsConstructor
public class IndexSearchServiceImpl implements IIndexSearchService {
final EsClientWrapper esClientWrapper;
/**
*
*
* @param searchDTO
*/
@Override
@Transactional(readOnly = true)
public Page<ESProductDTO> search(IndexSearchDTO searchDTO) throws IOException {
String indexName = "product_info";
/*// 查询索引
GetIndexResponse res = esClientWrapper.getEsClient().indices().get(request -> request.index(indexName));
System.err.println(res);
System.err.println(res.result());
Set<String> all = esClientWrapper.getEsClient().indices().get(req -> req.index("*")).result().keySet();
System.out.println("all = " + all);
GetResponse<ESProductInfo> response = esClientWrapper.getEsClient().get(g -> g.index(indexName).id("1"), ESProductInfo.class);
System.err.println(response);*/
// 分页查询
// 如果有搜索词,则要分词
if (StringUtils.isNotBlank(searchDTO.getSearch())) {
}
// 多个query 根据入参是否为空 分别赋值有multi_match , terms , range
/*SearchResponse<ESProductInfo> list = esClientWrapper.getEsClient().search(s -> s
.index(indexName)
.query(q -> q
.bool(b -> b
.must(m -> m
.multiMatch(mm -> mm
.query(searchDTO.getSearch())
.fields("prodTitle", "prodArtNum", "storeName"))
)
.filter(f -> f
.terms(t -> t
.field("prodCateId")
.terms(new TermsQueryField.Builder()
.value(searchDTO.getProdCateIdList().stream()
.map(IndexSearchServiceImpl::newFieldValue)
.collect(Collectors.toList()))
.build()
))
)
.filter(f -> f
.terms(t -> t
.field("parCateId")
.terms(new TermsQueryField.Builder()
.value(searchDTO.getParCateIdList().stream()
.map(IndexSearchServiceImpl::newFieldValue)
.collect(Collectors.toList()))
.build())))
.filter(f -> f
.terms(t -> t
.field("style.keyword")
.terms(new TermsQueryField.Builder()
.value(searchDTO.getStyleList().stream()
.map(IndexSearchServiceImpl::newFieldValue)
.collect(Collectors.toList()))
.build())))
.filter(f -> f
.terms(t -> t
.field("season.keyword")
.terms(new TermsQueryField.Builder()
.value(searchDTO.getSeasonList().stream()
.map(IndexSearchServiceImpl::newFieldValue)
.collect(Collectors.toList()))
.build())))
)
)
.from(0)
.size(10)
.sort(sort -> sort.field(f -> f.field("recommendWeight").order(SortOrder.Desc)))
,
ESProductInfo.class
);*/
// 构建 bool 查询
BoolQuery.Builder boolQuery = new BoolQuery.Builder();
// 添加 price 范围查询
if (searchDTO.getMinPrice() != null && searchDTO.getMaxPrice() != null) {
RangeQuery.Builder builder = new RangeQuery.Builder();
builder.number(NumberRangeQuery.of(n -> n.field("prodPrice").gte(Double.valueOf(searchDTO.getMinPrice()))
.lte(Double.valueOf(searchDTO.getMaxPrice()))));
boolQuery.filter(builder.build()._toQuery());
}
// 添加 multiMatch 查询
if (StringUtils.isNotBlank(searchDTO.getSearch())) {
MultiMatchQuery multiMatchQuery = MultiMatchQuery.of(m -> m
.query(searchDTO.getSearch())
.fields("prodTitle", "prodArtNum", "storeName")
);
boolQuery.must(multiMatchQuery._toQuery());
}
// 添加 prodCateId 过滤条件
if (searchDTO.getProdCateIdList() != null && !searchDTO.getProdCateIdList().isEmpty()) {
TermsQueryField termsQueryField = new TermsQueryField.Builder()
.value(searchDTO.getProdCateIdList().stream()
.map(IndexSearchServiceImpl::newFieldValue)
.collect(Collectors.toList()))
.build();
boolQuery.filter(f -> f.terms(t -> t.field("prodCateId").terms(termsQueryField)));
}
// 添加 parCateId 过滤条件
if (searchDTO.getParCateIdList() != null && !searchDTO.getParCateIdList().isEmpty()) {
TermsQueryField termsQueryField = new TermsQueryField.Builder()
.value(searchDTO.getParCateIdList().stream()
.map(IndexSearchServiceImpl::newFieldValue)
.collect(Collectors.toList()))
.build();
boolQuery.filter(f -> f.terms(t -> t.field("parCateId").terms(termsQueryField)));
}
// 添加 style 过滤条件
if (searchDTO.getStyleList() != null && !searchDTO.getStyleList().isEmpty()) {
TermsQueryField termsQueryField = new TermsQueryField.Builder()
.value(searchDTO.getStyleList().stream()
.map(IndexSearchServiceImpl::newFieldValue)
.collect(Collectors.toList()))
.build();
boolQuery.filter(f -> f.terms(t -> t.field("style.keyword").terms(termsQueryField)));
}
// 添加 season 过滤条件
if (searchDTO.getSeasonList() != null && !searchDTO.getSeasonList().isEmpty()) {
TermsQueryField termsQueryField = new TermsQueryField.Builder()
.value(searchDTO.getSeasonList().stream()
.map(IndexSearchServiceImpl::newFieldValue)
.collect(Collectors.toList()))
.build();
boolQuery.filter(f -> f.terms(t -> t.field("season.keyword").terms(termsQueryField)));
}
// 构建最终的查询
Query query = new Query.Builder().bool(boolQuery.build()).build();
// 执行搜索
SearchResponse<ESProductDTO> resList = esClientWrapper.getEsClient().search(s -> s.index(indexName)
.query(query).from(searchDTO.getPageNum() - 1).size(searchDTO.getPageSize())
.sort(sort -> sort.field(f -> f.field(searchDTO.getSort()).order(SortOrder.Desc))),
ESProductDTO.class);
return CollectionUtils.isEmpty(resList.hits().hits()) ? Page.empty(searchDTO.getPageSize(), searchDTO.getPageNum())
: Page.convert(new PageInfo<>(resList.hits().hits().stream().map(Hit::source).collect(Collectors.toList())));
}
private static FieldValue newFieldValue(String value) {
return FieldValue.of(value);
}
}