diff --git a/search/pom.xml b/search/pom.xml index 865c280c..410ab070 100644 --- a/search/pom.xml +++ b/search/pom.xml @@ -13,8 +13,13 @@ pom search-application - search-service-api - search-service-impl + search-biz + search-biz-api + + + search-rpc + search-rest + search-rpc-api diff --git a/search/search-application/pom.xml b/search/search-application/pom.xml index 1f0fbc32..0e839c13 100644 --- a/search/search-application/pom.xml +++ b/search/search-application/pom.xml @@ -15,92 +15,15 @@ cn.iocoder.mall - common-framework + search-rest 1.0-SNAPSHOT cn.iocoder.mall - mall-spring-boot + search-rpc 1.0-SNAPSHOT - - cn.iocoder.mall - user-sdk - 1.0-SNAPSHOT - - - cn.iocoder.mall - search-service-api - 1.0-SNAPSHOT - - - cn.iocoder.mall - search-service-impl - 1.0-SNAPSHOT - - - cn.iocoder.mall - system-service-api - 1.0-SNAPSHOT - - - - - org.springframework.boot - spring-boot-starter-web - - - - io.springfox - springfox-swagger2 - - - com.github.xiaoymin - swagger-bootstrap-ui - - - - - com.alibaba.cloud - spring-cloud-starter-alibaba-sentinel - - - - - - org.springframework.boot - spring-boot-starter-actuator - - - - io.micrometer - micrometer-registry-prometheus - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/search/search-application/src/main/java/cn/iocoder/mall/search/application/SearchApplication.java b/search/search-application/src/main/java/cn/iocoder/mall/search/application/SearchApplication.java index 228823e2..1f125797 100644 --- a/search/search-application/src/main/java/cn/iocoder/mall/search/application/SearchApplication.java +++ b/search/search-application/src/main/java/cn/iocoder/mall/search/application/SearchApplication.java @@ -2,13 +2,22 @@ package cn.iocoder.mall.search.application; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.config.ConfigFileApplicationListener; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.search"}) @EnableAsync(proxyTargetClass = true) public class SearchApplication { - + /** + * 设置需要读取的配置文件的名字。 + * 基于 {@link org.springframework.boot.context.config.ConfigFileApplicationListener#CONFIG_NAME_PROPERTY} 实现。 + */ + private static final String CONFIG_NAME_VALUE = "biz,rest,rpc,application"; public static void main(String[] args) { + + // 设置环境变量 + System.setProperty(ConfigFileApplicationListener.CONFIG_NAME_PROPERTY, CONFIG_NAME_VALUE); + // 解决 ES java.lang.IllegalStateException: availableProcessors is already System.setProperty("es.set.netty.runtime.available.processors", "false"); SpringApplication.run(SearchApplication.class, args); diff --git a/search/search-application/src/main/java/cn/iocoder/mall/search/application/controller/users/UsersProductSearchController.java b/search/search-application/src/main/java/cn/iocoder/mall/search/application/controller/users/UsersProductSearchController.java deleted file mode 100644 index 50835667..00000000 --- a/search/search-application/src/main/java/cn/iocoder/mall/search/application/controller/users/UsersProductSearchController.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.iocoder.mall.search.application.controller.users; - -import cn.iocoder.common.framework.util.StringUtil; -import cn.iocoder.common.framework.vo.CommonResult; -import cn.iocoder.common.framework.vo.SortingField; -import cn.iocoder.mall.search.api.ProductSearchService; -import cn.iocoder.mall.search.api.bo.ProductConditionBO; -import cn.iocoder.mall.search.api.bo.ProductPageBO; -import cn.iocoder.mall.search.api.dto.ProductConditionDTO; -import cn.iocoder.mall.search.api.dto.ProductSearchPageDTO; -import org.apache.dubbo.config.annotation.Reference; -import io.swagger.annotations.Api; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.util.Collections; - -import static cn.iocoder.common.framework.vo.CommonResult.success; - -// TODO 芋艿,搜索关键字的配置 -// TODO 芋艿,搜索日志 - -@RestController -@RequestMapping("users/product") -@Api("商品搜索") -public class UsersProductSearchController { - - @Reference(validation = "true", version = "${dubbo.provider.ProductSearchService.version}") - private ProductSearchService productSearchService; - - @GetMapping("/page") // TODO 芋艿,后面把 BO 改成 VO - public CommonResult page(@RequestParam(value = "cid", required = false) Integer cid, - @RequestParam(value = "keyword", required = false) String keyword, - @RequestParam(value = "pageNo", required = false) Integer pageNo, - @RequestParam(value = "pageSize", required = false) Integer pageSize, - @RequestParam(value = "sortField", required = false) String sortField, - @RequestParam(value = "sortOrder", required = false) String sortOrder) { - // 创建 ProductSearchPageDTO 对象 - ProductSearchPageDTO productSearchPageDTO = new ProductSearchPageDTO().setCid(cid).setKeyword(keyword) - .setPageNo(pageNo).setPageSize(pageSize); - if (StringUtil.hasText(sortField) && StringUtil.hasText(sortOrder)) { - productSearchPageDTO.setSorts(Collections.singletonList(new SortingField(sortField, sortOrder))); - } - // 执行搜索 - return success(productSearchService.getSearchPage(productSearchPageDTO)); - } - - @GetMapping("/condition") // TODO 芋艿,后面把 BO 改成 VO - public CommonResult condition(@RequestParam(value = "keyword", required = false) String keyword) { - // 创建 ProductConditionDTO 对象 - ProductConditionDTO productConditionDTO = new ProductConditionDTO().setKeyword(keyword) - .setFields(Collections.singleton(ProductConditionDTO.FIELD_CATEGORY)); - // 执行搜索 - return success(productSearchService.getSearchCondition(productConditionDTO)); - } - -} diff --git a/search/search-application/src/main/resources/application.yaml b/search/search-application/src/main/resources/application.yaml index c531b15d..4ddc8d28 100644 --- a/search/search-application/src/main/resources/application.yaml +++ b/search/search-application/src/main/resources/application.yaml @@ -1,29 +1,6 @@ spring: application: name: search-application - - # Spring Cloud 配置项 - cloud: - # Spring Cloud Sentinel 配置项 - sentinel: - transport: - dashboard: s1.iocoder.cn:12088 # Sentinel Dashboard 服务地址 - eager: true # 项目启动时,直接连接到 Sentinel - -# server -server: - port: 18086 - servlet: - context-path: /search-api/ - -swagger: - enable: false - - -management: - endpoints: - web: - exposure: - include: health,info,env,metrics,prometheus - metrics: - enabled: true + # Profile 的配置项 + profiles: + active: local diff --git a/search/search-biz-api/pom.xml b/search/search-biz-api/pom.xml new file mode 100644 index 00000000..b8d93c0d --- /dev/null +++ b/search/search-biz-api/pom.xml @@ -0,0 +1,23 @@ + + + + search + cn.iocoder.mall + 1.0-SNAPSHOT + + 4.0.0 + + search-biz-api + + + + + cn.iocoder.mall + common-framework + 1.0-SNAPSHOT + + + + diff --git a/search/search-biz/pom.xml b/search/search-biz/pom.xml new file mode 100644 index 00000000..7a6c27c5 --- /dev/null +++ b/search/search-biz/pom.xml @@ -0,0 +1,110 @@ + + + + search + cn.iocoder.mall + 1.0-SNAPSHOT + + 4.0.0 + + search-biz + + + + + cn.iocoder.mall + mall-spring-boot + 1.0-SNAPSHOT + + + + search-biz-api + cn.iocoder.mall + 1.0-SNAPSHOT + + + + cn.iocoder.mall + product-rpc-api + 1.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-data-elasticsearch + + + + + com.alibaba.cloud + spring-cloud-starter-dubbo + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-stream-rocketmq + + + + + com.google.guava + guava + + + + org.mapstruct + mapstruct + + + org.mapstruct + mapstruct-jdk8 + + + org.projectlombok + lombok + + + + com.alibaba + fastjson + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-web + + + + + com.github.vanroy + spring-boot-starter-data-jest + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + diff --git a/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/bo/ProductBO.java b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/bo/ProductBO.java new file mode 100644 index 00000000..7ba30104 --- /dev/null +++ b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/bo/ProductBO.java @@ -0,0 +1,86 @@ +package cn.iocoder.mall.search.biz.bo; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +/** + * 商品 ES BO + */ +@Data +@Accessors(chain = true) +public class ProductBO implements Serializable { + + private Integer id; + + // ========== 基本信息 ========= + /** + * SPU 名字 + */ + private String name; + /** + * 卖点 + */ + private String sellPoint; + /** + * 描述 + */ + private String description; + /** + * 分类编号 + */ + private Integer cid; + /** + * 分类名 + */ + private String categoryName; + /** + * 商品主图地数组 + */ + private List picUrls; + + // ========== 其他信息 ========= + /** + * 是否上架商品(是否可见)。 + * + * true 为已上架 + * false 为已下架 + */ + private Boolean visible; + /** + * 排序字段 + */ + private Integer sort; + + // ========== Sku 相关字段 ========= + /** + * 原价格,单位:分 + */ + private Integer originalPrice; + /** + * 购买价格,单位:分。 + */ + private Integer buyPrice; + /** + * 库存数量 + */ + private Integer quantity; + + // ========== 促销活动相关字段 ========= + // 目前只促销单体商品促销,目前仅限制折扣。 + /** + * 促销活动编号 + */ + private Integer promotionActivityId; + /** + * 促销活动标题 + */ + private String promotionActivityTitle; + /** + * 促销活动类型 + */ + private Integer promotionActivityType; + +} diff --git a/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/bo/ProductConditionBO.java b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/bo/ProductConditionBO.java new file mode 100644 index 00000000..b8eebbfe --- /dev/null +++ b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/bo/ProductConditionBO.java @@ -0,0 +1,35 @@ +package cn.iocoder.mall.search.biz.bo; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 商品搜索条件返回 BO + */ +@Data +@Accessors(chain = true) +public class ProductConditionBO { + + /** + * 商品分类数组 + */ + private List categories; + + @Data + @Accessors(chain = true) + public static class Category { + + /** + * 分类编号 + */ + private Integer id; + /** + * 分类名称 + */ + private String name; + + } + +} diff --git a/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/bo/ProductPageBO.java b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/bo/ProductPageBO.java new file mode 100644 index 00000000..827b7996 --- /dev/null +++ b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/bo/ProductPageBO.java @@ -0,0 +1,22 @@ +package cn.iocoder.mall.search.biz.bo; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +@Data +@Accessors(chain = true) +public class ProductPageBO implements Serializable { + + /** + * 管理员数组 + */ + private List list; + /** + * 总量 + */ + private Integer total; + +} diff --git a/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/config/JPAConfiguration.java b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/config/JPAConfiguration.java new file mode 100644 index 00000000..acb35fd2 --- /dev/null +++ b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/config/JPAConfiguration.java @@ -0,0 +1,9 @@ +package cn.iocoder.mall.search.biz.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; + +@Configuration +@EnableElasticsearchRepositories(basePackages = "cn.iocoder.mall.search.biz.dao") +public class JPAConfiguration { +} diff --git a/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/constant/FieldAnalyzer.java b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/constant/FieldAnalyzer.java new file mode 100644 index 00000000..5b531576 --- /dev/null +++ b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/constant/FieldAnalyzer.java @@ -0,0 +1,26 @@ +package cn.iocoder.mall.search.biz.constant; + +/** + * ES 字段分析器的枚举类 + * + * 关于 IK 分词,文章 https://blog.csdn.net/xsdxs/article/details/72853288 不错。 + * 目前项目使用的 ES 版本是 6.7.1 ,可以在 https://www.elastic.co/cn/downloads/past-releases/elasticsearch-6-7-1 下载。 + * 如果不知道怎么安装 ES ,可以看 https://blog.csdn.net/chengyuqiang/article/details/78837712 简单。 + */ +public class FieldAnalyzer { + + /** + * IK 最大化分词 + * + * 会将文本做最细粒度的拆分 + */ + public static final String IK_MAX_WORD = "ik_max_word"; + + /** + * IK 智能分词 + * + * 会做最粗粒度的拆分 + */ + public static final String IK_SMART = "ik_smart"; + +} diff --git a/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/convert/ProductSearchConvert.java b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/convert/ProductSearchConvert.java new file mode 100644 index 00000000..9227f361 --- /dev/null +++ b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/convert/ProductSearchConvert.java @@ -0,0 +1,17 @@ +package cn.iocoder.mall.search.biz.convert; + +import cn.iocoder.mall.search.biz.bo.ProductBO; +import cn.iocoder.mall.search.biz.dataobject.ESProductDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface ProductSearchConvert { + + ProductSearchConvert INSTANCE = Mappers.getMapper(ProductSearchConvert.class); + + List convert(List list); + +} diff --git a/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/dao/ProductRepository.java b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/dao/ProductRepository.java new file mode 100644 index 00000000..1b4b869d --- /dev/null +++ b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/dao/ProductRepository.java @@ -0,0 +1,69 @@ +package cn.iocoder.mall.search.biz.dao; + +import cn.iocoder.common.framework.util.CollectionUtil; +import cn.iocoder.common.framework.util.StringUtil; +import cn.iocoder.common.framework.vo.SortingField; +import cn.iocoder.mall.search.biz.dataobject.ESProductDO; +import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; +import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders; +import org.elasticsearch.search.sort.SortBuilders; +import org.elasticsearch.search.sort.SortOrder; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +import static org.elasticsearch.index.query.QueryBuilders.matchQuery; + +@Repository +public interface ProductRepository extends ElasticsearchRepository { + + @Deprecated + ESProductDO findByName(String name); + + default Page search(Integer cid, String keyword, Integer pageNo, Integer pageSize, + List sortFields) { + NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder() + .withPageable(PageRequest.of(pageNo - 1, pageSize)); + // 筛选条件 cid + if (cid != null) { + nativeSearchQueryBuilder.withFilter(QueryBuilders.termQuery("cid", cid)); + } + // 筛选 + if (StringUtil.hasText(keyword)) { + FunctionScoreQueryBuilder.FilterFunctionBuilder[] functions = { // TODO 芋艿,分值随便打的 + new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("name", keyword), + ScoreFunctionBuilders.weightFactorFunction(10)), + new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("sellPoint", keyword), + ScoreFunctionBuilders.weightFactorFunction(2)), + new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("categoryName", keyword), + ScoreFunctionBuilders.weightFactorFunction(3)), +// new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("description", keyword), +// ScoreFunctionBuilders.weightFactorFunction(2)), // TODO 芋艿,目前这么做,如果商品描述很长,在按照价格降序,会命中超级多的关键字。 + }; + FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(functions) + .scoreMode(FunctionScoreQuery.ScoreMode.SUM) + .setMinScore(2F); // TODO 芋艿,需要考虑下 score + nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder); + } else { + nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery()); + } + // 排序 + if (!CollectionUtil.isEmpty(sortFields)) { + sortFields.forEach(sortField -> nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField.getField()) + .order(SortOrder.fromString(sortField.getOrder())))); + } else if (StringUtil.hasText(keyword)) { + nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC)); + } else { + nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("sort").order(SortOrder.DESC)); + } + // 执行查询 + return search(nativeSearchQueryBuilder.build()); + } + +} diff --git a/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/dataobject/ESProductDO.java b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/dataobject/ESProductDO.java new file mode 100644 index 00000000..bd06b0ab --- /dev/null +++ b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/dataobject/ESProductDO.java @@ -0,0 +1,96 @@ +package cn.iocoder.mall.search.biz.dataobject; + +import cn.iocoder.mall.search.biz.constant.FieldAnalyzer; +import lombok.Data; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import java.util.List; + +/** + * 商品 ES DO + */ +@Document(indexName = "product", type = "spu", shards = 1, replicas = 0) +@Data +@Accessors(chain = true) +public class ESProductDO { + + @Id + private Integer id; + + // ========== 基本信息 ========= + /** + * SPU 名字 + */ + @Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text) + private String name; + /** + * 卖点 + */ + @Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text) + private String sellPoint; + /** + * 描述 + */ + @Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text) + private String description; + /** + * 分类编号 + */ + private Integer cid; + /** + * 分类名 + */ + @Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text) + private String categoryName; + /** + * 商品主图地数组 + */ + private List picUrls; + + // ========== 其他信息 ========= + /** + * 是否上架商品(是否可见)。 + * + * true 为已上架 + * false 为已下架 + */ + private Boolean visible; + /** + * 排序字段 + */ + private Integer sort; + + // ========== Sku 相关字段 ========= + /** + * 原价格,单位:分 + */ + private Integer originalPrice; + /** + * 购买价格,单位:分。 + */ + private Integer buyPrice; + /** + * 库存数量 + */ + private Integer quantity; + + // ========== 促销活动相关字段 ========= + // 目前只促销单体商品促销,目前仅限制折扣。 + /** + * 促销活动编号 + */ + private Integer promotionActivityId; + /** + * 促销活动标题 + */ + private String promotionActivityTitle; + /** + * 促销活动类型 + */ + private Integer promotionActivityType; + +} diff --git a/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/dto/ProductConditionDTO.java b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/dto/ProductConditionDTO.java new file mode 100644 index 00000000..4bfe0fea --- /dev/null +++ b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/dto/ProductConditionDTO.java @@ -0,0 +1,29 @@ +package cn.iocoder.mall.search.biz.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.Collection; + +/** + * 获得商品检索条件 DTO + */ +@Data +@Accessors(chain = true) +public class ProductConditionDTO { + + /** + * Field - 商品分类 + */ + public static final String FIELD_CATEGORY = "category"; + + /** + * 关键字 + */ + private String keyword; + /** + * 需要返回的搜索条件的 fields 名 + */ + private Collection fields; + +} diff --git a/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/dto/ProductSearchPageDTO.java b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/dto/ProductSearchPageDTO.java new file mode 100644 index 00000000..430a32ed --- /dev/null +++ b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/dto/ProductSearchPageDTO.java @@ -0,0 +1,43 @@ +package cn.iocoder.mall.search.biz.dto; + +import cn.iocoder.common.framework.util.CollectionUtil; +import cn.iocoder.common.framework.vo.SortingField; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; +import java.util.Set; + +/** + * 商品检索分页 DTO + */ +@Data +@Accessors(chain = true) +public class ProductSearchPageDTO { + + public static final Set SORT_FIELDS = CollectionUtil.asSet("buyPrice"); + + /** + * 分类编号 + */ + private Integer cid; + /** + * 关键字 + */ + private String keyword; + + /** + * 页码 + */ + private Integer pageNo; + /** + * 分页大小 + */ + private Integer pageSize; + + /** + * 排序字段数组 + */ + private List sorts; + +} diff --git a/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/service/ProductSearchService.java b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/service/ProductSearchService.java new file mode 100644 index 00000000..50101d74 --- /dev/null +++ b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/service/ProductSearchService.java @@ -0,0 +1,20 @@ +package cn.iocoder.mall.search.biz.service; + + +public interface ProductSearchService { + + Integer rebuild(); + + /** + * 构建商品的搜索索引 + * + * @param id 商品编号 + * @return 构建结果 + */ + Boolean save(Integer id); +// +// ProductPageBO getSearchPage(ProductSearchPageDTO searchPageDTO); +// +// ProductConditionBO getSearchCondition(ProductConditionDTO conditionDTO); + +} diff --git a/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/service/ProductSearchServiceImpl.java b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/service/ProductSearchServiceImpl.java new file mode 100644 index 00000000..af4b0880 --- /dev/null +++ b/search/search-biz/src/main/java/cn/iocoder/mall/search/biz/service/ProductSearchServiceImpl.java @@ -0,0 +1,147 @@ +package cn.iocoder.mall.search.biz.service; + +import cn.iocoder.common.framework.util.CollectionUtil; +import cn.iocoder.common.framework.vo.SortingField; +import cn.iocoder.mall.search.biz.dao.ProductRepository; +import cn.iocoder.mall.search.biz.dto.ProductSearchPageDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.util.List; + +@Service +@org.apache.dubbo.config.annotation.Service(validation = "true", version = "${dubbo.provider.ProductSearchService.version}") +public class ProductSearchServiceImpl implements ProductSearchService { + + private static final Integer REBUILD_FETCH_PER_SIZE = 100; + + @Autowired + private ProductRepository productRepository; + @Autowired + private ElasticsearchTemplate elasticsearchTemplate; // 因为需要使用到聚合操作,只好引入 ElasticsearchTemplate 。 + +// @Reference(validation = "true", version = "${dubbo.consumer.ProductSpuService.version}") +// private ProductSpuService productSpuService; +// @Reference(validation = "true", version = "${dubbo.consumer.ProductCategoryService.version}") +// private ProductCategoryService productCategoryService; +// @Reference(validation = "true", version = "${dubbo.consumer.CartService.version}") +// private CartService cartService; + +// @Override +// public Integer rebuild() { +// // TODO 芋艿,因为目前商品比较少,所以写的很粗暴。等未来重构 +// Integer lastId = null; +// int rebuildCounts = 0; +// while (true) { +// List spus = productSpuService.getProductSpuDetailListForSync(lastId, REBUILD_FETCH_PER_SIZE); +// rebuildCounts += spus.size(); +// // 存储到 ES 中 +// List products = spus.stream().map(this::convert).collect(Collectors.toList()); +// productRepository.saveAll(products); +// // 设置新的 lastId ,或者结束 +// if (spus.size() < REBUILD_FETCH_PER_SIZE) { +// break; +// } else { +// lastId = spus.get(spus.size() - 1).getId(); +// } +// } +// // 返回成功 +// return rebuildCounts; +// } +// +// @Override +// public Boolean save(Integer id) { +// // 获得商品性情 +// ProductSpuDetailBO result = productSpuService.getProductSpuDetail(id); +// // 存储到 ES 中 +// ESProductDO product = convert(result); +// productRepository.save(product); +// // 返回成功 +// return true; +// } +// +// @SuppressWarnings("OptionalGetWithoutIsPresent") +// private ESProductDO convert(ProductSpuDetailBO spu) { +// // 获得最小价格的 SKU ,用于下面的价格计算 +// ProductSpuDetailBO.Sku sku = spu.getSkus().stream().min(Comparator.comparing(ProductSpuDetailBO.Sku::getPrice)).get(); +// // 价格计算 +// CalcSkuPriceBO calSkuPriceResult = cartService.calcSkuPrice(sku.getId()); +// // 拼装结果 +// return ProductSearchConvert.INSTANCE.convert(spu, calSkuPriceResult); +// } +// +// @Override +// public ProductPageBO getSearchPage(ProductSearchPageDTO searchPageDTO) { +// checkSortFieldInvalid(searchPageDTO.getSorts()); +// // 执行查询 +// Page searchPage = productRepository.search(searchPageDTO.getCid(), searchPageDTO.getKeyword(), +// searchPageDTO.getPageNo(), searchPageDTO.getPageSize(), searchPageDTO.getSorts()); +// // 转换结果 +// return new ProductPageBO() +// .setList(ProductSearchConvert.INSTANCE.convert(searchPage.getContent())) +// .setTotal((int) searchPage.getTotalElements()); +// } + + private void checkSortFieldInvalid(List sorts) { + if (CollectionUtil.isEmpty(sorts)) { + return; + } + sorts.forEach(sortingField -> Assert.isTrue(ProductSearchPageDTO.SORT_FIELDS.contains(sortingField.getField()), + String.format("排序字段(%s) 不在允许范围内", sortingField.getField()))); + } + + @Override + public Integer rebuild() { + return null; + } + + @Override + public Boolean save(Integer id) { + return null; + } + +// @Override +// public ProductConditionBO getSearchCondition(ProductConditionDTO conditionDTO) { +// // 创建 ES 搜索条件 +// NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); +// // 筛选 +// if (StringUtil.hasText(conditionDTO.getKeyword())) { // 如果有 keyword ,就去匹配 +// nativeSearchQueryBuilder.withQuery(QueryBuilders.multiMatchQuery(conditionDTO.getKeyword(), +// "name", "sellPoint", "categoryName")); +// } else { +// nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery()); +// } +// // 聚合 +// if (conditionDTO.getFields().contains(ProductConditionDTO.FIELD_CATEGORY)) { // 商品分类 +// nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("cids").field("cid")); +// } +// // 执行查询 +// ProductConditionBO condition = elasticsearchTemplate.query(nativeSearchQueryBuilder.build(), response -> { +// ProductConditionBO result = new ProductConditionBO(); +// // categoryIds 聚合 +// Aggregation categoryIdsAggregation = response.getAggregations().get("cids"); +// if (categoryIdsAggregation != null) { +// result.setCategories(new ArrayList<>()); +// for (LongTerms.Bucket bucket : (((LongTerms) categoryIdsAggregation).getBuckets())) { +// result.getCategories().add(new ProductConditionBO.Category().setId(bucket.getKeyAsNumber().intValue())); +// } +// } +// // 返回结果 +// return result; +// }); +// // 聚合其它数据源 +// if (!CollectionUtil.isEmpty(condition.getCategories())) { +// // 查询指定的 ProductCategoryBO 数组,并转换成 ProductCategoryBO Map +// Map categoryMap = productCategoryService.getListByIds( +// condition.getCategories().stream().map(ProductConditionBO.Category::getId).collect(Collectors.toList())) +// .stream().collect(Collectors.toMap(ProductCategoryBO::getId, category -> category)); +// // 设置分类名 +// condition.getCategories().forEach(category -> category.setName(categoryMap.get(category.getId()).getName())); +// } +// // 返回结果 +// return condition; +// } + +} diff --git a/search/search-biz/src/main/resources/biz.properties b/search/search-biz/src/main/resources/biz.properties new file mode 100644 index 00000000..197907af --- /dev/null +++ b/search/search-biz/src/main/resources/biz.properties @@ -0,0 +1,8 @@ +##################### 业务模块 ##################### +## OAuth2CodeService +modules.oauth2-code-service.access-token-expire-time-millis = 2880000 +modules.oauth2-code-service.refresh-token-expire-time-millis = 43200000 +## OAuth2MobileCodeService +modules.oauth2-mobile-code-service.code-expire-time-millis = 600000 +modules.oauth2-mobile-code-service.send-maximum-quantity-per-day = 10 +modules.oauth2-mobile-code-service.send-frequency = 60000 diff --git a/search/search-biz/src/main/resources/biz.yaml b/search/search-biz/src/main/resources/biz.yaml new file mode 100644 index 00000000..fc41b8a9 --- /dev/null +++ b/search/search-biz/src/main/resources/biz.yaml @@ -0,0 +1,7 @@ +spring: + data: + # Jest 配置项 + jest: + uri: http://127.0.0.1:9200 + + diff --git a/search/search-rest/pom.xml b/search/search-rest/pom.xml new file mode 100644 index 00000000..fa0bbe14 --- /dev/null +++ b/search/search-rest/pom.xml @@ -0,0 +1,42 @@ + + + + search + cn.iocoder.mall + 1.0-SNAPSHOT + + 4.0.0 + + search-rest + 提供 商品搜索服务的 Rest 接口的实现,提供对外调用 + + + + + cn.iocoder.mall + search-biz + 1.0-SNAPSHOT + + + + + cn.iocoder.mall + mall-spring-boot-starter-web + 1.0-SNAPSHOT + + + cn.iocoder.mall + mall-spring-boot-starter-security + 1.0-SNAPSHOT + + + cn.iocoder.mall + mall-spring-boot-starter-swagger + 1.0-SNAPSHOT + + + + + \ No newline at end of file diff --git a/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/controller/user/UsersProductSearchController.java b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/controller/user/UsersProductSearchController.java new file mode 100644 index 00000000..9a44420b --- /dev/null +++ b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/controller/user/UsersProductSearchController.java @@ -0,0 +1,72 @@ +package cn.iocoder.mall.search.rest.controller.user; + +import cn.iocoder.common.framework.constant.MallConstants; +import cn.iocoder.common.framework.util.StringUtil; +import cn.iocoder.common.framework.vo.CommonResult; +import cn.iocoder.common.framework.vo.SortingField; +import cn.iocoder.mall.search.biz.service.ProductSearchService; +import cn.iocoder.mall.search.rest.response.user.ProductPageResponse; +import io.swagger.annotations.Api; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import cn.iocoder.mall.search.rest.request.user.ProductConditionRequest; +import cn.iocoder.mall.search.rest.request.user.ProductSearchPageRequest; +import cn.iocoder.mall.search.rest.response.user.ProductConditionResponse; + +import java.util.Collections; + +import static cn.iocoder.common.framework.vo.CommonResult.success; + +/** + * Created with IDEA + * + * @author : lhl + * @version : 1.0 + * @Time : 19:26 + * @date : 2020/5/14 + */ +@RestController +@RequestMapping(MallConstants.ROOT_PATH_ADMIN + "users/product") +@Api(tags = "商品查询 API") +@Slf4j +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class UsersProductSearchController { + + + private final ProductSearchService productSearchService; + + @GetMapping("/page") // TODO 芋艿,后面把 BO 改成 VO + public CommonResult page(@RequestParam(value = "cid", required = false) Integer cid, + @RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "pageNo", required = false) Integer pageNo, + @RequestParam(value = "pageSize", required = false) Integer pageSize, + @RequestParam(value = "sortField", required = false) String sortField, + @RequestParam(value = "sortOrder", required = false) String sortOrder) { + // 创建 ProductSearchPageDTO 对象 + ProductSearchPageRequest productSearchPageDTO = new ProductSearchPageRequest().setCid(cid).setKeyword(keyword) + .setPageNo(pageNo).setPageSize(pageSize); + if (StringUtil.hasText(sortField) && StringUtil.hasText(sortOrder)) { + productSearchPageDTO.setSorts(Collections.singletonList(new SortingField(sortField, sortOrder))); + } + // 执行搜索 +// return success(productSearchService.getSearchPage(productSearchPageDTO)); + return success(null); + } + + @GetMapping("/condition") // TODO 芋艿,后面把 BO 改成 VO + public CommonResult condition(@RequestParam(value = "keyword", required = false) String keyword) { + // 创建 ProductConditionDTO 对象 + ProductConditionRequest productConditionDTO = new ProductConditionRequest().setKeyword(keyword) + .setFields(Collections.singleton(ProductConditionRequest.FIELD_CATEGORY)); + // 执行搜索 +// return success(productSearchService.getSearchCondition(productConditionDTO)); + return success(null); + } + + +} diff --git a/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/convert/user/UsersProductSearchConvert.java b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/convert/user/UsersProductSearchConvert.java new file mode 100644 index 00000000..4fcdac3b --- /dev/null +++ b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/convert/user/UsersProductSearchConvert.java @@ -0,0 +1,35 @@ +package cn.iocoder.mall.search.rest.convert.user; + +import cn.iocoder.mall.search.biz.bo.ProductBO; +import cn.iocoder.mall.search.biz.dataobject.ESProductDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface UsersProductSearchConvert { + + cn.iocoder.mall.search.biz.convert.ProductSearchConvert INSTANCE = Mappers.getMapper(cn.iocoder.mall.search.biz.convert.ProductSearchConvert.class); + +// @Mappings({}) +// ESProductDO convert(ProductSpuDetailBO spu); + +// @Mappings({}) +// default ESProductDO convert(ProductSpuDetailBO spu, CalcSkuPriceBO calcSkuPrice) { +// // Spu 的基础数据 +// ESProductDO product = this.convert(spu); +// product.setOriginalPrice(calcSkuPrice.getOriginalPrice()).setBuyPrice(calcSkuPrice.getBuyPrice()); +// // 设置促销活动相关字段 +// if (calcSkuPrice.getTimeLimitedDiscount() != null) { +// PromotionActivityBO activity = calcSkuPrice.getTimeLimitedDiscount(); +// product.setPromotionActivityId(activity.getId()).setPromotionActivityTitle(activity.getTitle()) +// .setPromotionActivityType(activity.getActivityType()); +// } +// // 返回 +// return product; +// } + + List convert(List list); + +} diff --git a/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/request/user/UsersProductConditionRequest.java b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/request/user/UsersProductConditionRequest.java new file mode 100644 index 00000000..c90b0b95 --- /dev/null +++ b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/request/user/UsersProductConditionRequest.java @@ -0,0 +1,29 @@ +package cn.iocoder.mall.search.rest.request.user; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.Collection; + +/** + * 获得商品检索条件 DTO + */ +@Data +@Accessors(chain = true) +public class UsersProductConditionRequest{ + + /** + * Field - 商品分类 + */ + public static final String FIELD_CATEGORY = "category"; + + /** + * 关键字 + */ + private String keyword; + /** + * 需要返回的搜索条件的 fields 名 + */ + private Collection fields; + +} diff --git a/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/request/user/UsersProductSearchPageRequest.java b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/request/user/UsersProductSearchPageRequest.java new file mode 100644 index 00000000..bdb2c52f --- /dev/null +++ b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/request/user/UsersProductSearchPageRequest.java @@ -0,0 +1,48 @@ +package cn.iocoder.mall.search.rest.request.user; + +import cn.iocoder.common.framework.util.CollectionUtil; +import cn.iocoder.common.framework.vo.SortingField; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; +import java.util.Set; + +/** + * Created with IDEA + * + * @author : lhl + * @version : 1.0 + * @Time : 19:09 + * @date : 2020/5/14 + */ +@Data +@Accessors(chain = true) +public class UsersProductSearchPageRequest { + + public static final Set SORT_FIELDS = CollectionUtil.asSet("buyPrice"); + + /** + * 分类编号 + */ + private Integer cid; + /** + * 关键字 + */ + private String keyword; + + /** + * 页码 + */ + private Integer pageNo; + /** + * 分页大小 + */ + private Integer pageSize; + + /** + * 排序字段数组 + */ + private List sorts; + +} diff --git a/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/response/user/UsersProductConditionResponse.java b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/response/user/UsersProductConditionResponse.java new file mode 100644 index 00000000..4b008a8f --- /dev/null +++ b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/response/user/UsersProductConditionResponse.java @@ -0,0 +1,35 @@ +package cn.iocoder.mall.search.rest.response.user; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 商品搜索条件返回 BO + */ +@Data +@Accessors(chain = true) +public class UsersProductConditionResponse { + + /** + * 商品分类数组 + */ + private List categories; + + @Data + @Accessors(chain = true) + public static class Category { + + /** + * 分类编号 + */ + private Integer id; + /** + * 分类名称 + */ + private String name; + + } + +} diff --git a/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/response/user/UsersProductPageResponse.java b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/response/user/UsersProductPageResponse.java new file mode 100644 index 00000000..56ece146 --- /dev/null +++ b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/response/user/UsersProductPageResponse.java @@ -0,0 +1,22 @@ +package cn.iocoder.mall.search.rest.response.user; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +@Data +@Accessors(chain = true) +public class UsersProductPageResponse implements Serializable { + + /** + * 管理员数组 + */ + private List list; + /** + * 总量 + */ + private Integer total; + +} diff --git a/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/response/user/UsersProductResponse.java b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/response/user/UsersProductResponse.java new file mode 100644 index 00000000..5d5127fc --- /dev/null +++ b/search/search-rest/src/main/java/cn/iocoder/mall/search/rest/response/user/UsersProductResponse.java @@ -0,0 +1,86 @@ +package cn.iocoder.mall.search.rest.response.user; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +/** + * 商品 ES BO + */ +@Data +@Accessors(chain = true) +public class UsersProductResponse implements Serializable { + + private Integer id; + + // ========== 基本信息 ========= + /** + * SPU 名字 + */ + private String name; + /** + * 卖点 + */ + private String sellPoint; + /** + * 描述 + */ + private String description; + /** + * 分类编号 + */ + private Integer cid; + /** + * 分类名 + */ + private String categoryName; + /** + * 商品主图地数组 + */ + private List picUrls; + + // ========== 其他信息 ========= + /** + * 是否上架商品(是否可见)。 + * + * true 为已上架 + * false 为已下架 + */ + private Boolean visible; + /** + * 排序字段 + */ + private Integer sort; + + // ========== Sku 相关字段 ========= + /** + * 原价格,单位:分 + */ + private Integer originalPrice; + /** + * 购买价格,单位:分。 + */ + private Integer buyPrice; + /** + * 库存数量 + */ + private Integer quantity; + + // ========== 促销活动相关字段 ========= + // 目前只促销单体商品促销,目前仅限制折扣。 + /** + * 促销活动编号 + */ + private Integer promotionActivityId; + /** + * 促销活动标题 + */ + private String promotionActivityTitle; + /** + * 促销活动类型 + */ + private Integer promotionActivityType; + +} diff --git a/search/search-rest/src/main/resources/rest.yaml b/search/search-rest/src/main/resources/rest.yaml new file mode 100644 index 00000000..778308fc --- /dev/null +++ b/search/search-rest/src/main/resources/rest.yaml @@ -0,0 +1,12 @@ +# 服务器的配置项 +server: + port: 18099 + servlet: + context-path: /search-api/ + +# Swagger 配置项 +swagger: + title: 商品查询子系统 + description: 商品查询子系统 + version: 1.0.0 + base-package: cn.iocoder.mall.search.rest.controller diff --git a/search/search-rpc-api/pom.xml b/search/search-rpc-api/pom.xml new file mode 100644 index 00000000..2dd3c6c5 --- /dev/null +++ b/search/search-rpc-api/pom.xml @@ -0,0 +1,33 @@ + + + + search + cn.iocoder.mall + 1.0-SNAPSHOT + + 4.0.0 + + search-rpc-api + + + + + + search-biz-api + cn.iocoder.mall + 1.0-SNAPSHOT + + + + + javax.validation + validation-api + + + org.projectlombok + lombok + + + \ No newline at end of file diff --git a/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/api/user/ProductSearchRPC.java b/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/api/user/ProductSearchRPC.java new file mode 100644 index 00000000..57a7e83a --- /dev/null +++ b/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/api/user/ProductSearchRPC.java @@ -0,0 +1,21 @@ +package cn.iocoder.mall.search.biz.api.user; + +import cn.iocoder.common.framework.vo.CommonResult; + +public interface ProductSearchRPC { + + CommonResult rebuild(); + + /** + * 构建商品的搜索索引 + * + * @param id 商品编号 + * @return 构建结果 + */ + CommonResult save(Integer id); + +// ProductPageBO getSearchPage(ProductSearchPageDTO searchPageDTO); +// +// ProductConditionBO getSearchCondition(ProductConditionDTO conditionDTO); + +} diff --git a/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/request/user/ProductConditionRequest.java b/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/request/user/ProductConditionRequest.java new file mode 100644 index 00000000..eb9fd74e --- /dev/null +++ b/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/request/user/ProductConditionRequest.java @@ -0,0 +1,29 @@ +package cn.iocoder.mall.search.biz.request.user; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.Collection; + +/** + * 获得商品检索条件 DTO + */ +@Data +@Accessors(chain = true) +public class ProductConditionRequest { + + /** + * Field - 商品分类 + */ + public static final String FIELD_CATEGORY = "category"; + + /** + * 关键字 + */ + private String keyword; + /** + * 需要返回的搜索条件的 fields 名 + */ + private Collection fields; + +} diff --git a/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/request/user/ProductSearchPageRequest.java b/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/request/user/ProductSearchPageRequest.java new file mode 100644 index 00000000..a722eea4 --- /dev/null +++ b/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/request/user/ProductSearchPageRequest.java @@ -0,0 +1,48 @@ +package cn.iocoder.mall.search.biz.request.user; + +import cn.iocoder.common.framework.util.CollectionUtil; +import cn.iocoder.common.framework.vo.SortingField; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; +import java.util.Set; + +/** + * Created with IDEA + * + * @author : lhl + * @version : 1.0 + * @Time : 19:09 + * @date : 2020/5/14 + */ +@Data +@Accessors(chain = true) +public class ProductSearchPageRequest { + + public static final Set SORT_FIELDS = CollectionUtil.asSet("buyPrice"); + + /** + * 分类编号 + */ + private Integer cid; + /** + * 关键字 + */ + private String keyword; + + /** + * 页码 + */ + private Integer pageNo; + /** + * 分页大小 + */ + private Integer pageSize; + + /** + * 排序字段数组 + */ + private List sorts; + +} diff --git a/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/response/user/ProductConditionResponse.java b/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/response/user/ProductConditionResponse.java new file mode 100644 index 00000000..2b48816f --- /dev/null +++ b/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/response/user/ProductConditionResponse.java @@ -0,0 +1,35 @@ +package cn.iocoder.mall.search.biz.response.user; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 商品搜索条件返回 BO + */ +@Data +@Accessors(chain = true) +public class ProductConditionResponse { + + /** + * 商品分类数组 + */ + private List categories; + + @Data + @Accessors(chain = true) + public static class Category { + + /** + * 分类编号 + */ + private Integer id; + /** + * 分类名称 + */ + private String name; + + } + +} diff --git a/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/response/user/ProductPageResponse.java b/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/response/user/ProductPageResponse.java new file mode 100644 index 00000000..56db936a --- /dev/null +++ b/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/response/user/ProductPageResponse.java @@ -0,0 +1,22 @@ +package cn.iocoder.mall.search.biz.response.user; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +@Data +@Accessors(chain = true) +public class ProductPageResponse implements Serializable { + + /** + * 管理员数组 + */ + private List list; + /** + * 总量 + */ + private Integer total; + +} diff --git a/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/response/user/ProductResponse.java b/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/response/user/ProductResponse.java new file mode 100644 index 00000000..31952582 --- /dev/null +++ b/search/search-rpc-api/src/main/java/cn/iocoder/mall/search/biz/response/user/ProductResponse.java @@ -0,0 +1,86 @@ +package cn.iocoder.mall.search.biz.response.user; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +/** + * 商品 ES BO + */ +@Data +@Accessors(chain = true) +public class ProductResponse implements Serializable { + + private Integer id; + + // ========== 基本信息 ========= + /** + * SPU 名字 + */ + private String name; + /** + * 卖点 + */ + private String sellPoint; + /** + * 描述 + */ + private String description; + /** + * 分类编号 + */ + private Integer cid; + /** + * 分类名 + */ + private String categoryName; + /** + * 商品主图地数组 + */ + private List picUrls; + + // ========== 其他信息 ========= + /** + * 是否上架商品(是否可见)。 + * + * true 为已上架 + * false 为已下架 + */ + private Boolean visible; + /** + * 排序字段 + */ + private Integer sort; + + // ========== Sku 相关字段 ========= + /** + * 原价格,单位:分 + */ + private Integer originalPrice; + /** + * 购买价格,单位:分。 + */ + private Integer buyPrice; + /** + * 库存数量 + */ + private Integer quantity; + + // ========== 促销活动相关字段 ========= + // 目前只促销单体商品促销,目前仅限制折扣。 + /** + * 促销活动编号 + */ + private Integer promotionActivityId; + /** + * 促销活动标题 + */ + private String promotionActivityTitle; + /** + * 促销活动类型 + */ + private Integer promotionActivityType; + +} diff --git a/search/search-rpc/pom.xml b/search/search-rpc/pom.xml new file mode 100644 index 00000000..d46e9e2f --- /dev/null +++ b/search/search-rpc/pom.xml @@ -0,0 +1,48 @@ + + + + search + cn.iocoder.mall + 1.0-SNAPSHOT + + 4.0.0 + + search-rpc + + + + + + cn.iocoder.mall + search-rpc-api + 1.0-SNAPSHOT + + + + cn.iocoder.mall + search-biz + 1.0-SNAPSHOT + + + + + com.alibaba.cloud + spring-cloud-starter-dubbo + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.github.vanroy + spring-boot-starter-data-jest + + + + \ No newline at end of file diff --git a/search/search-rpc/src/main/java/cn/iocoder/mall/search/biz/convert/ProductSearchConvert.java b/search/search-rpc/src/main/java/cn/iocoder/mall/search/biz/convert/ProductSearchConvert.java new file mode 100644 index 00000000..b8314290 --- /dev/null +++ b/search/search-rpc/src/main/java/cn/iocoder/mall/search/biz/convert/ProductSearchConvert.java @@ -0,0 +1,35 @@ +package cn.iocoder.mall.search.biz.convert; + +import cn.iocoder.mall.search.biz.bo.ProductBO; +import cn.iocoder.mall.search.biz.dataobject.ESProductDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface ProductSearchConvert { + + ProductSearchConvert INSTANCE = Mappers.getMapper(ProductSearchConvert.class); + +// @Mappings({}) +// ESProductDO convert(ProductSpuDetailBO spu); + +// @Mappings({}) +// default ESProductDO convert(ProductSpuDetailBO spu, CalcSkuPriceBO calcSkuPrice) { +// // Spu 的基础数据 +// ESProductDO product = this.convert(spu); +// product.setOriginalPrice(calcSkuPrice.getOriginalPrice()).setBuyPrice(calcSkuPrice.getBuyPrice()); +// // 设置促销活动相关字段 +// if (calcSkuPrice.getTimeLimitedDiscount() != null) { +// PromotionActivityBO activity = calcSkuPrice.getTimeLimitedDiscount(); +// product.setPromotionActivityId(activity.getId()).setPromotionActivityTitle(activity.getTitle()) +// .setPromotionActivityType(activity.getActivityType()); +// } +// // 返回 +// return product; +// } + + List convert(List list); + +} diff --git a/search/search-rpc/src/main/java/cn/iocoder/mall/search/biz/rpc/user/ProductSearchRPCImpl.java b/search/search-rpc/src/main/java/cn/iocoder/mall/search/biz/rpc/user/ProductSearchRPCImpl.java new file mode 100644 index 00000000..129068bd --- /dev/null +++ b/search/search-rpc/src/main/java/cn/iocoder/mall/search/biz/rpc/user/ProductSearchRPCImpl.java @@ -0,0 +1,29 @@ +package cn.iocoder.mall.search.biz.rpc.user; + +import cn.iocoder.common.framework.vo.CommonResult; +import cn.iocoder.mall.search.biz.api.user.ProductSearchRPC; +import cn.iocoder.mall.search.biz.service.ProductSearchService; +import org.apache.dubbo.config.annotation.Service; +import org.springframework.beans.factory.annotation.Autowired; + + +@Service(validation = "true", version = "${dubbo.provider.ProductSearchRpc.version}") +public class ProductSearchRPCImpl implements ProductSearchRPC { + + @Autowired + private ProductSearchService productSearchService; + + @Override + public CommonResult rebuild() { + return null; + } + + @Override + public CommonResult save(Integer id){ +// ProductSpuDetailBO productSpuDetail = productSpuService.getProductSpuDetail(spuId); +// return ProductSpuConvert.INSTANCE.convertDetail(productSpuDetail); + return CommonResult.success(true); + } + + +} diff --git a/search/search-rpc/src/main/resources/rpc-local.yaml b/search/search-rpc/src/main/resources/rpc-local.yaml new file mode 100644 index 00000000..e056170a --- /dev/null +++ b/search/search-rpc/src/main/resources/rpc-local.yaml @@ -0,0 +1,14 @@ +spring: + # Spring Cloud 配置项 + cloud: + nacos: + # Spring Cloud Nacos Discovery 配置项 + discovery: + server-addr: s1.iocoder.cn:8848 # Nacos 服务器地址 + namespace: local # Nacos 命名空间 + +# Dubbo 配置项 +dubbo: + # Dubbo 注册中心 + registry: + address: spring-cloud://s1.iocoder.cn:8848 # 指定 Dubbo 服务注册中心的地址 diff --git a/search/search-rpc/src/main/resources/rpc-test.yaml b/search/search-rpc/src/main/resources/rpc-test.yaml new file mode 100644 index 00000000..d3d0e9e6 --- /dev/null +++ b/search/search-rpc/src/main/resources/rpc-test.yaml @@ -0,0 +1,14 @@ +spring: + # Spring Cloud 配置项 + cloud: + nacos: + # Spring Cloud Nacos Discovery 配置项 + discovery: + server-addr: s1.iocoder.cn:8848 # Nacos 服务器地址 + namespace: test # Nacos 命名空间 + +# Dubbo 配置项 +dubbo: + # Dubbo 注册中心 + registry: + address: spring-cloud://s1.iocoder.cn:8848 # 指定 Dubbo 服务注册中心的地址 diff --git a/search/search-rpc/src/main/resources/rpc.yaml b/search/search-rpc/src/main/resources/rpc.yaml new file mode 100644 index 00000000..398fbf43 --- /dev/null +++ b/search/search-rpc/src/main/resources/rpc.yaml @@ -0,0 +1,22 @@ +# Dubbo 配置项 +dubbo: + # Spring Cloud Alibaba Dubbo 专属配置 + cloud: + subscribed-services: 'search-application' # 设置订阅的应用列表,默认为 * 订阅所有应用 + # Dubbo 提供者的协议 + protocol: + name: dubbo + port: -1 + # Dubbo 提供服务的扫描基础包 + scan: + base-packages: cn.iocoder.mall.search.rpc.rpc + # Dubbo 服务提供者的配置 + provider: +# filter: -exception +# ProductSpuService: +# version: 1.0.0 + + # Dubbo 服务消费者的配置 + consumer: +# ProductSpuService: +# version: 1.0.0 diff --git a/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/ProductSearchService.java b/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/ProductSearchService.java index ca4f714a..6d4bbf1d 100644 --- a/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/ProductSearchService.java +++ b/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/ProductSearchService.java @@ -1,9 +1,9 @@ -package cn.iocoder.mall.search.api; +package cn.iocoder.mall.search.biz; -import cn.iocoder.mall.search.api.bo.ProductConditionBO; -import cn.iocoder.mall.search.api.bo.ProductPageBO; -import cn.iocoder.mall.search.api.dto.ProductConditionDTO; -import cn.iocoder.mall.search.api.dto.ProductSearchPageDTO; +import cn.iocoder.mall.search.biz.bo.ProductConditionBO; +import cn.iocoder.mall.search.biz.bo.ProductPageBO; +import cn.iocoder.mall.search.biz.dto.ProductConditionDTO; +import cn.iocoder.mall.search.biz.dto.ProductSearchPageDTO; public interface ProductSearchService { diff --git a/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/bo/ProductBO.java b/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/bo/ProductBO.java index acaa15fc..7ba30104 100644 --- a/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/bo/ProductBO.java +++ b/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/bo/ProductBO.java @@ -1,4 +1,4 @@ -package cn.iocoder.mall.search.api.bo; +package cn.iocoder.mall.search.biz.bo; import lombok.Data; import lombok.experimental.Accessors; diff --git a/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/bo/ProductConditionBO.java b/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/bo/ProductConditionBO.java index 238b0dbb..b8eebbfe 100644 --- a/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/bo/ProductConditionBO.java +++ b/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/bo/ProductConditionBO.java @@ -1,4 +1,4 @@ -package cn.iocoder.mall.search.api.bo; +package cn.iocoder.mall.search.biz.bo; import lombok.Data; import lombok.experimental.Accessors; diff --git a/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/bo/ProductPageBO.java b/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/bo/ProductPageBO.java index b18871a5..827b7996 100644 --- a/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/bo/ProductPageBO.java +++ b/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/bo/ProductPageBO.java @@ -1,4 +1,4 @@ -package cn.iocoder.mall.search.api.bo; +package cn.iocoder.mall.search.biz.bo; import lombok.Data; import lombok.experimental.Accessors; diff --git a/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/dto/ProductConditionDTO.java b/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/dto/ProductConditionDTO.java index ff1d54c0..4bfe0fea 100644 --- a/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/dto/ProductConditionDTO.java +++ b/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/dto/ProductConditionDTO.java @@ -1,4 +1,4 @@ -package cn.iocoder.mall.search.api.dto; +package cn.iocoder.mall.search.biz.dto; import lombok.Data; import lombok.experimental.Accessors; diff --git a/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/dto/ProductSearchPageDTO.java b/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/dto/ProductSearchPageDTO.java index bfc465bd..430a32ed 100644 --- a/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/dto/ProductSearchPageDTO.java +++ b/search/search-service-api/src/main/java/cn/iocoder/mall/search/api/dto/ProductSearchPageDTO.java @@ -1,4 +1,4 @@ -package cn.iocoder.mall.search.api.dto; +package cn.iocoder.mall.search.biz.dto; import cn.iocoder.common.framework.util.CollectionUtil; import cn.iocoder.common.framework.vo.SortingField; diff --git a/search/search-service-impl/src/main/java/cn/iocoder/mall/search/biz/convert/ProductSearchConvert.java b/search/search-service-impl/src/main/java/cn/iocoder/mall/search/biz/convert/ProductSearchConvert.java index ed239635..6a42e33c 100644 --- a/search/search-service-impl/src/main/java/cn/iocoder/mall/search/biz/convert/ProductSearchConvert.java +++ b/search/search-service-impl/src/main/java/cn/iocoder/mall/search/biz/convert/ProductSearchConvert.java @@ -3,7 +3,7 @@ package cn.iocoder.mall.search.biz.convert; import cn.iocoder.mall.order.api.bo.CalcSkuPriceBO; import cn.iocoder.mall.product.api.bo.ProductSpuDetailBO; import cn.iocoder.mall.promotion.api.bo.PromotionActivityBO; -import cn.iocoder.mall.search.api.bo.ProductBO; +import cn.iocoder.mall.search.biz.bo.ProductBO; import cn.iocoder.mall.search.biz.dataobject.ESProductDO; import org.mapstruct.Mapper; import org.mapstruct.Mappings; diff --git a/search/search-service-impl/src/main/java/cn/iocoder/mall/search/biz/mq/PayTransactionPaySuccessConsumer.java b/search/search-service-impl/src/main/java/cn/iocoder/mall/search/biz/mq/PayTransactionPaySuccessConsumer.java index 81d4fa6e..d2b3156e 100644 --- a/search/search-service-impl/src/main/java/cn/iocoder/mall/search/biz/mq/PayTransactionPaySuccessConsumer.java +++ b/search/search-service-impl/src/main/java/cn/iocoder/mall/search/biz/mq/PayTransactionPaySuccessConsumer.java @@ -1,7 +1,7 @@ package cn.iocoder.mall.search.biz.mq; import cn.iocoder.mall.product.api.message.ProductUpdateMessage; -import cn.iocoder.mall.search.api.ProductSearchService; +import cn.iocoder.mall.search.biz.ProductSearchService; import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; import org.apache.rocketmq.spring.core.RocketMQListener; import org.springframework.beans.factory.annotation.Autowired; diff --git a/search/search-service-impl/src/main/java/cn/iocoder/mall/search/biz/service/ProductSearchServiceImpl.java b/search/search-service-impl/src/main/java/cn/iocoder/mall/search/biz/service/ProductSearchServiceImpl.java index 1a787a8a..1adda27c 100644 --- a/search/search-service-impl/src/main/java/cn/iocoder/mall/search/biz/service/ProductSearchServiceImpl.java +++ b/search/search-service-impl/src/main/java/cn/iocoder/mall/search/biz/service/ProductSearchServiceImpl.java @@ -1,4 +1,4 @@ -package cn.iocoder.mall.search.biz.service; +package cn.iocoder.mall.search.biz.api; import cn.iocoder.common.framework.util.CollectionUtil; import cn.iocoder.common.framework.util.StringUtil; @@ -9,11 +9,11 @@ import cn.iocoder.mall.product.api.ProductCategoryService; import cn.iocoder.mall.product.api.ProductSpuService; import cn.iocoder.mall.product.api.bo.ProductCategoryBO; import cn.iocoder.mall.product.api.bo.ProductSpuDetailBO; -import cn.iocoder.mall.search.api.ProductSearchService; -import cn.iocoder.mall.search.api.bo.ProductConditionBO; -import cn.iocoder.mall.search.api.bo.ProductPageBO; -import cn.iocoder.mall.search.api.dto.ProductConditionDTO; -import cn.iocoder.mall.search.api.dto.ProductSearchPageDTO; +import cn.iocoder.mall.search.biz.ProductSearchService; +import cn.iocoder.mall.search.biz.bo.ProductConditionBO; +import cn.iocoder.mall.search.biz.bo.ProductPageBO; +import cn.iocoder.mall.search.biz.dto.ProductConditionDTO; +import cn.iocoder.mall.search.biz.dto.ProductSearchPageDTO; import cn.iocoder.mall.search.biz.convert.ProductSearchConvert; import cn.iocoder.mall.search.biz.dao.ProductRepository; import cn.iocoder.mall.search.biz.dataobject.ESProductDO; diff --git a/search/search-service-impl/src/test/java/cn/iocoder/mall/search/biz/service/ProductSearchServiceImplTest.java b/search/search-service-impl/src/test/java/cn/iocoder/mall/search/biz/service/ProductSearchServiceImplTest.java index 44047855..06ffc3b2 100644 --- a/search/search-service-impl/src/test/java/cn/iocoder/mall/search/biz/service/ProductSearchServiceImplTest.java +++ b/search/search-service-impl/src/test/java/cn/iocoder/mall/search/biz/service/ProductSearchServiceImplTest.java @@ -1,4 +1,4 @@ -package cn.iocoder.mall.search.biz.service; +package cn.iocoder.mall.search.biz.api; import cn.iocoder.mall.search.biz.dao.ProductRepository; import org.junit.Test;