diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/WebsiteController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/WebsiteController.java new file mode 100644 index 000000000..562af2662 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/WebsiteController.java @@ -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(@Validated @RequestBody IndexSearchVO searchVO) throws IOException { + return R.ok(searchService.search(BeanUtil.toBean(searchVO, IndexSearchDTO.class))); + } + + + +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/website/IndexSearchVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/website/IndexSearchVO.java new file mode 100644 index 000000000..6bec97471 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/website/IndexSearchVO.java @@ -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 parCateIdList; + @ApiModelProperty(value = "二级类目ID列表") + private List prodCateIdList; + @ApiModelProperty(value = "最小价格") + private String minPrice; + @ApiModelProperty(value = "最大价格") + private String maxPrice; + @ApiModelProperty(value = "风格列表") + private List styleList; + @ApiModelProperty(value = "季节列表") + private List seasonList; + @ApiModelProperty(value = "排序") + private String sort; + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/es/ESProductDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/es/ESProductDTO.java new file mode 100644 index 000000000..ce2d0bad3 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/es/ESProductDTO.java @@ -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 tags; + @ApiModelProperty(value = "标题") + private String prodTitle; + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/website/IndexSearchDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/website/IndexSearchDTO.java new file mode 100644 index 000000000..4693db047 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/website/IndexSearchDTO.java @@ -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 parCateIdList; + @ApiModelProperty(value = "二级类目ID列表") + private List prodCateIdList; + @ApiModelProperty(value = "最小价格") + private String minPrice; + @ApiModelProperty(value = "最大价格") + private String maxPrice; + @ApiModelProperty(value = "风格列表") + private List styleList; + @ApiModelProperty(value = "季节列表") + private List seasonList; + @ApiModelProperty(value = "排序") + private String sort; + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IIndexSearchService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IIndexSearchService.java new file mode 100644 index 000000000..ea15e1cfa --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IIndexSearchService.java @@ -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 search(IndexSearchDTO searchDTO) throws IOException; + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/IndexSearchServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/IndexSearchServiceImpl.java new file mode 100644 index 000000000..aa2299801 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/IndexSearchServiceImpl.java @@ -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 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 all = esClientWrapper.getEsClient().indices().get(req -> req.index("*")).result().keySet(); + System.out.println("all = " + all); + + GetResponse 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 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 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); + } + + +}