谷粒商城学习笔记(六)--搜索

1、搜索

在这里插入图片描述
搜索:计算机根据用户输入的关键词进行匹配,从已有的数据库中摘录出相关的记录反馈给用户。

若使用传统关系型数据库:
1、对于传统的关系性数据库对于关键词的查询,只能逐字逐行的匹配,性能非常差。
2、匹配方式不合理,比如搜索“小密手机” ,如果用like进行匹配, 根本匹配不到。但是考虑使用者的用户体验的话,除了完全匹配的记录,还应该显示一部分近似匹配的记录,至少应该匹配到“手机”。
在这里插入图片描述
使用专业全文索引进行搜索:
全文搜索引擎目前主流的索引技术就是倒排索引的方式。-----用内容去匹配索引
传统的保存数据的方式都是:记录→单词
而倒排索引的保存数据的方式是:单词→记录
在这里插入图片描述

搜索引擎匹配搜索:
处理分词、构建倒排索引都通过Lucene实现。

1、基于分词技术构建倒排索引:-----在内容上建立索引,用内容去匹配索引---B+树
	首先每个记录保存数据时,都不会直接存入数据库。
	系统先会对数据进行分词,然后以倒排索引结构保存。
2、等到用户搜索的时候,会把搜索的关键词也进行分词,会把“红海行动”分词分成:红海和行动两个词。

这样的话,先用红海进行匹配,得到id=1和id=2的记录编号,再用行动匹配可以迅速定位id为1,3的记录。
那么全文索引通常,还会根据匹配程度进行打分,显然1号记录能匹配的次数更多。
所以显示的时候以评分进行排序的话,1号记录会排到最前面。而2、3号记录也可以匹配到。

2、全文检索工具Elasticsearch

lucene只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的服务框架搭建起来的应用。------lucene是类似于jdk,而搜索引擎软件就是tomcat

Elasticsearch是一个基于Apache Lucene™的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。
特点:
1、分布式的实时文件存储,每个字段都被索引并可被搜索
2、分布式的实时分析搜索引擎–做不规则查询
3、可以扩展到上百台服务器,处理PB级结构化或非结构化数据

Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

ES能做什么?
全文检索(全部字段)、模糊查询(搜索)、数据分析(提供分析语法,例如聚合)

elasticsearch和solr,----都是基于lucene搭建的,可以独立部署启动的搜索引擎服务软件
	国内百度、京东、新浪都是基于elasticSearch实现的搜索功能。
	国外就更多了 像维基百科、GitHub、Stack Overflow等等也都是基于ES的
	区别:
		1. Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能;
		2. Solr 支持更多格式的数据,而 Elasticsearch 仅支持json文件格式;
		3. Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供;
		4. Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch--附近的人

将ES 配置在虚拟机上:
–比较烦人,要切换root和其它用户。修改默认线程数、最大文件数、最大内存数

修改四个地方:
	elasticSearch.yml es的启动host地址
	jvm.options配置es的虚拟机内存
	limits.conf配置linux的线程内存和文件
	sysctl.conf配置系统允许的软件运行内存

ES基本概念:
在这里插入图片描述

1、cluster	整个elasticsearch 默认就是集群状态,整个集群是一份完整、互备的数据。
2、node	集群中的一个节点,一般只一个进程就是一个node
3、shard	分片,即使是一个节点中的数据也会通过hash算法,分成多个片存放,默认是5片。
4、Index(库)	相当于rdbms的database, 对于用户来说是一个逻辑数据库,虽然物理上会被分多个shard存放,也可能存放在多个node中。
5、Type(表)	类似于rdbms的table,但是与其说像table,其实更像面向对象中的class , 同一Json的格式的数据集合。
6、Document(一条数据)	类似于rdbms的 row、面向对象里的object
7、Field(字段)	相当于字段、属性

通过(9200端口)http协议进行交互:http://192.168.199.129:9200/_cat/indices?v
开发工具 Kibana(5601端口):配置host、es.url,—nohup ./kibana &

ES简单的增删改查
PUT、DELETE、POST、GET

在这里插入图片描述
中文分词

elasticsearch本身自带的中文分词,就是单纯把中文一个字一个字的分开,根本没有词汇的概念。
安装中文分词器ik。
ik(中英文分词器)有两个:
1 ik_smart(简易分词–最少切分)我、是、中国人
2 ik_max_word(尽最大可能分词–最细粒度划分)我、是、中国人、中国、人

相关性算分

指文档与查询语句间的相关度,通过倒排索引可以获取与查询语句相匹配的文档列表

如何将最符合用户查询需求的文档放到前列呢?
	本质问题是一个排序的问题,排序的依据是相关性算分,确定倒排索引哪个文档排在前面

影响相关度算分的参数:
	1、TF(Term Frequency):词频,即单词在文档中出现的次数,词频越高,相关度越高
	2、Document Frequency(DF):文档词频,即单词出现的文档数
	3、IDF(Inverse Document Frequency):逆向文档词频,与文档词频相反,即1/DF。
		即单词出现的文档数越少,相关度越高(如果一个单词在文档集出现越少,算为越重要单词)
	4、Field-length Norm:文档越短,相关度越高

TF/IDE模型、BM25模型

ElasticSearch集群
克隆一个虚拟机做集群。–修改配置文件elasticserach.yml

1、简介

一个节点(node)就是一个Elasticsearch实例,
而一个集群(cluster)由一个或多个节点组成,它们具有相同的cluster.name,它们协同工作,分享数据和负载。
当加入新的节点或者删除一个节点时,集群就会感知到并平衡数据(同步)。

几个基本概念:
	1、节点:一个节点就是一个es的服务器,
		es集群中,主节点负责集群的管理和任务的分发,一般不负责文档的增删改查
	2、片:分片是es的实际物理存储单元(一个lucene的实例)
	3、索引:索引是es的逻辑单元,一个索引一般建立在多个不同机器的分片上
	4、复制片:每个机器的分片一般在其他机器上会有两到三个复制片(目的是提高数据的容错率)
	5、容错:一旦集群中的某些机器发生故障,那么剩余的机器会在主机点的管理下,重新分配资源(分片)
	6、分片的路由:写操作(新建、删除)只在主分片上进行,然后将结果同步给复制分片
		Sync 主分片同步给复制成功后,才返回结果给客户端
		Async 主分片在操作成功后,在同步复制分片的同时返回成功结果给客户端
		读操作(查询)可以在主分片或者复制分片上进行

2、节点

1、集群中一个节点会被选举为主节点(master)
2、主节点临时管理集群级别的一些变更,例如新建或删除索引、增加或移除节点等。
3、主节点不参与文档级别的变更或搜索,这意味着在流量增长的时候,该主节点不会成为集群的瓶颈。
4、任何节点都可以成为主节点。
5、用户,我们能够与集群中的任何节点通信,包括主节点。
6、每一个节点都知道文档存在于哪个节点上,它们可以转发请求到相应的节点上。
7、我们访问的节点负责收集各节点返回的数据,最后一起返回给客户端。这一切都由Elasticsearch处理。

3、集群健康

集群健康有三种状态:green、yellow或red。
green	所有主要分片和复制分片都可用
yellow	所有主要分片可用,但不是所有复制分片都可用
red	不是所有的主要分片都可用

4、集群分片

索引只是一个用来指向一个或多个分片(shards)的“逻辑命名空间(logical namespace)”.

分片(shard)是一个最小级别“工作单元(worker unit)”,它只是保存了索引中所有数据的一部分,是一个Lucene实例,并且它本身就是一个完整的搜索引擎。
文档存储在分片中,并且在分片中被索引,但应用程序不会直接与分片通信,而是直接与索引通信。

1、主分片
索引中的每个文档属于一个单独的主分片,所以主分片的数量决定了索引最多能存储多少数据。
	理论上主分片能存储的数据大小是没有限制的,限制取决于你实际的使用情况。
	分片的最大容量完全取决于你的使用状况:硬件存储的大小、文档的大小和复杂度、如何索引和查询你的文档,以及你期望的响应时间。
2、副分片
复制分片只是主分片的一个副本,它可以防止硬件故障导致的数据丢失,
同时可以提供读请求,比如搜索或者从别的shard取回文档。

当索引创建完成的时候,主分片的数量就固定了,但是复制分片的数量可以随时调整。

3、搜索模块

1、整合es到项目

以Rest Api为主的missing client,最典型的就是jest。
Jest客户端可以直接使用dsl语句拼成的字符串,直接传给服务端,然后返回json字符串再解析。

在search-service中引入jest和jna的pom依赖。
在parent中将版本号纳入管理。
spring-boot-starter-data-elasticsearch不用管理版本号,版本跟随springboot。

配置文件中配置jest:spring.elasticsearch.jest.uris=http://192.168.199.129:9200

2、得到商品列表

1、通过首页的3级分类进入,按照分类id查询对应的属性和属性值列表。
2、直接通过搜索栏输入文字进入,根据sku的查询结果涉及的属性值,再去查询数据库显示属性文字列表。

实现步骤:

1、数据结构的准备

通过ES的mapping定义商品的数据结构:

ES的mapping定义-----基于整个库
MySQL数据结构字段定义------基于整个表

数据结构:

	1 商品名称(展示/查询)
	2 商品价格(展示/查询)
	3 商品图片(展示)
	4 平台属性和属性值的列表(查询)
	5 商品描述(展示/查询)
	6 热度值(查询)
	7 三级分类id(查询)
	8 商品id
	9 主键

参数结构:

关键字(商品名称(展示/查询) 5 商品描述(展示/查询) 2 商品价格(展示/查询))
平台属性和属性值的列表(查询)
三级分类id(查询)

在这里插入图片描述

代码实现:
在这里插入图片描述

PUT gmall
{
 "mappings": {
   "PmsSkuInfo":{//表名
     "properties": {//属性
       "id":{
        "type": "keyword",
        "index": true
      },
      "skuName":{
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "skuDesc":{
        "type": "text"
        , "analyzer": "ik_smart"
      },
      "catalog3Id":{
        "type": "keyword"
      },
      "price":{
        "type": "double"
      },
      "skuDefaultImg":{
        "type": "keyword",
        "index": false
      },
      "hotScore":{
        "type": "double"
      },
      "productId":{
        "type": "keyword"
      },
      "skuAttrValueList":{
        "properties": {
          "attrId":{
            "type":"keyword"
          },
          "valueId":{
            "type":"keyword"
          }
        }
      }
     } 
   }
 } 
}

2、初始化项目

搜索页面平台属性列表

平台属性列表是从搜索结果中抽取出来的,不是根据三级分类id查询的所有平台属性的集合
1、es中使用aggs聚合函数抽取平台属性—aggs与query平级
对skuAttrValueList中的valueId进行聚合
在这里插入图片描述

TermsBuilder groupby_attr = AggregationBuilders.terms("groupby_attr").field("skuAttrValueList.valueId");
searchSourceBuilder.aggregation(groupby_attr);

2、使用java代码抽取平台属性
A 根据skuId去mysql中查询平台属性值的id集合(不推荐)
B 直接用java集合进行处理—用set集合将不重复的属性值id抽取出来

        //调用搜索服务--list,返回搜索结果
        List<PmsSearchSkuInfo> pmsSearchSkuInfos = searchService.list(pmsSearchParam);
        modelMap.put("skuLsInfoList", pmsSearchSkuInfos);

        //根据检索的结果抽取所包含的平台属性集合 --- 不建议使用mysql查询---set性能更高
        Set<String> valueIdSet = new HashSet<>();//通过哈希表完成去重

        for (PmsSearchSkuInfo pmsSearchSkuInfo : pmsSearchSkuInfos) {
            List<PmsSkuAttrValue> skuAttrValueList = pmsSearchSkuInfo.getSkuAttrValueList();
            for (PmsSkuAttrValue pmsSkuAttrValue : skuAttrValueList) {
                String valueId = pmsSkuAttrValue.getValueId();
                valueIdSet.add(valueId);
            }
        }

        //调用属性服务AttrService根据属性值id将平台属性的集合列表查询出来,通过modelMap传给页面
        List<PmsBaseAttrInfo> pmsBaseAttrInfos = attrService.getAttrValueListByValueId(valueIdSet);
        modelMap.put("attrList", pmsBaseAttrInfos);

SearchSeviceImpl:

    @Override//通过给定搜索参数pmsSearchParam,返回查询到的商品信息
    public List<PmsSearchSkuInfo> list(PmsSearchParam pmsSearchParam) {
        String dslStr = getSearchDsl(pmsSearchParam);//通过辅助函数将es查询到的
        System.err.println(dslStr);
        // 用api执行复杂查询
        List<PmsSearchSkuInfo> pmsSearchSkuInfos = new ArrayList<>();
        Search search = new Search.Builder(dslStr).addIndex("gmall0105").addType("PmsSkuInfo").build();
        SearchResult execute = null;
        try {
            execute = jestClient.execute(search);
        } catch (IOException e) {
            e.printStackTrace();
        }
        List<SearchResult.Hit<PmsSearchSkuInfo, Void>> hits = execute.getHits(PmsSearchSkuInfo.class);
        for (SearchResult.Hit<PmsSearchSkuInfo, Void> hit : hits) {
            PmsSearchSkuInfo source = hit.source;//从结果中取出数据本身
            Map<String, List<String>> highlight = hit.highlight;//从es查询到的结果中,hit中取出高亮字段--highlight里面的是红的
            if(highlight != null){
                String skuName = highlight.get("skuName").get(0);
                source.setSkuName(skuName);//取出红色的字放到source中
            }
            pmsSearchSkuInfos.add(source);
        }

        System.out.println(pmsSearchSkuInfos.size());
        return pmsSearchSkuInfos;
    }

    //定义复杂查询的辅助函数--给定查询参数,返回字符串
    private String getSearchDsl(PmsSearchParam pmsSearchParam) {
        //从给定的查询参数pmsSearchParam中取出这三个内容
        String[] skuAttrValueList = pmsSearchParam.getValueId();
        String keyword = pmsSearchParam.getKeyword();
        String catalog3Id = pmsSearchParam.getCatalog3Id();

        // jest的dsl的封装工具类   ---//使用jest将写好的dsl语句包装起来,传给es运行
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        /**
         * dsl-查询前过滤格式:
         *
         * Query{
         *   Bool:{// 先过滤,后查询
         *     Filter:{term,term}
         *     must:{match}
         *   }
         * }
         *
         */
        // bool
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();//将filter和must封装在bool里面
        // filter
        if(StringUtils.isNotBlank(catalog3Id)){
            TermQueryBuilder termQueryBuilder = new TermQueryBuilder("catalog3Id",catalog3Id);//term条件
            boolQueryBuilder.filter(termQueryBuilder);
        }
        if(skuAttrValueList!=null){
            for (String pmsSkuAttrValue : skuAttrValueList) {
                TermQueryBuilder termQueryBuilder = new TermQueryBuilder("skuAttrValueList.valueId",pmsSkuAttrValue);
                boolQueryBuilder.filter(termQueryBuilder);//过滤
            }
        }
        // must
        if(StringUtils.isNotBlank(keyword)){
            MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("skuName",keyword);//match条件
            boolQueryBuilder.must(matchQueryBuilder);//必须符合
        }
        // query
        searchSourceBuilder.query(boolQueryBuilder);//将bool放在query里面

        // highlight----将查到的keyWord进行高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<span style='color:red;'>");//前缀
        highlightBuilder.field("skuName");//字段-----对skuName进行高亮
        highlightBuilder.postTags("</span>");//后缀
        searchSourceBuilder.highlight(highlightBuilder);

        // sort -- 对查询到的结果进行排序,按照id降序    SortOrder是一个常量类
        searchSourceBuilder.sort("id", SortOrder.DESC);
        // from
        searchSourceBuilder.from(0);
        // size
        searchSourceBuilder.size(20);

        //aggs
        //--- es中使用aggs聚合函数抽取平台属性 ---损失查询性能
        //es中aggs与query平级---"groupby_attr"是别名,"skuAttrValueList.valueId"是聚合字段
        TermsBuilder groupby_attr = AggregationBuilders.terms("groupby_attr").field("skuAttrValueList.valueId");
        searchSourceBuilder.aggregation(groupby_attr);


        return searchSourceBuilder.toString();//将查询到的结果封装成字符串
    }

面包屑:

在这里插入图片描述

1 当前请求url中所包含的属性=面包屑中所包含的属性
2 属性列表中的属性是排除了当前请求(面包屑请求)中的属性的剩余属性
3 去除一个选中属性:当点击面包屑后,新请求url=当前请求url-被点击面包屑
4 增加一个选中属性:当点击属性列表后,新请求url=当前请求+被点击的属性列表
当前请求的url的参数就是pmsSearchParam是所提交的参数
在这里插入图片描述
在这里插入图片描述

@Controller
public class SearchController {

    @Reference
    SearchService searchService;

    @Reference
    AttrService attrService;

    @RequestMapping("list.html")
    public String list(PmsSearchParam pmsSearchParam, ModelMap modelMap) {//三级分类ID、关键字

        //调用搜索服务--list,返回搜索结果
        List<PmsSearchSkuInfo> pmsSearchSkuInfos = searchService.list(pmsSearchParam);
        modelMap.put("skuLsInfoList", pmsSearchSkuInfos);

        //根据检索的结果抽取所包含的平台属性集合 --- 不建议使用mysql查询---set性能更高
        Set<String> valueIdSet = new HashSet<>();//通过哈希表完成去重

        for (PmsSearchSkuInfo pmsSearchSkuInfo : pmsSearchSkuInfos) {
            List<PmsSkuAttrValue> skuAttrValueList = pmsSearchSkuInfo.getSkuAttrValueList();
            for (PmsSkuAttrValue pmsSkuAttrValue : skuAttrValueList) {
                String valueId = pmsSkuAttrValue.getValueId();
                valueIdSet.add(valueId);
            }
        }

        //调用属性服务AttrService根据属性值id将平台属性的集合列表查询出来,通过modelMap传给页面
        List<PmsBaseAttrInfo> pmsBaseAttrInfos = attrService.getAttrValueListByValueId(valueIdSet);
        modelMap.put("attrList", pmsBaseAttrInfos);

//---------面包屑------------------

        //对平台属性集合进一步处理,去掉当前条件中valueId所在的属性组
        String[] delValueIds = pmsSearchParam.getValueId();

        if (delValueIds != null) {

            //面包屑:用户所点击过的平台属性
            //面包屑请求 :当前请求 – 当前面包屑的属性 = 新url

            List<PmsSearchCrumb> pmsSearchCrumbs = new ArrayList<>();


            for (String delValueId : delValueIds) {//将面包屑功能进行整合 ===循环前后的顺序可以换

                //--这里要将iterator放进循环里面
                // Iterator是一个独立的迭代器,不是集合本身,只负责用游标指向集合某一个元素,适合做检查式的删除
                //平台属性集合,每次都要重新扫一遍,迭代器循环完一遍就没了,会导致第二个面包屑扫不到值---要把迭代器放里面
                Iterator<PmsBaseAttrInfo> iterator = pmsBaseAttrInfos.iterator();

                //生成面包屑的参数PmsSearchCrumb包含属性值、属性Id和地址参数
                PmsSearchCrumb pmsSearchCrumb = new PmsSearchCrumb();
                pmsSearchCrumb.setValueId(delValueId);
                pmsSearchCrumb.setUrlParam(getUrlParamForCrumb(pmsSearchParam, delValueId));

                while (iterator.hasNext()) {
                    PmsBaseAttrInfo pmsBaseAttrInfo = iterator.next();
                    List<PmsBaseAttrValue> attrValueList = pmsBaseAttrInfo.getAttrValueList();
                    for (PmsBaseAttrValue pmsBaseAttrValue : attrValueList) {
                        String valueId = pmsBaseAttrValue.getId();


                        if (valueId.equals(delValueId)) {
                            //查找面包屑的属性值名称
                            pmsSearchCrumb.setValueName(pmsBaseAttrValue.getValueName());

                            //删除当前valueId所在的属性组
                            iterator.remove();
                        }
                    }
                }
                pmsSearchCrumbs.add(pmsSearchCrumb);
            }
            modelMap.put("attrValueSelectedList", pmsSearchCrumbs);//把面包屑集合传给页面
        }


        //如果使用ArrayList去删除,最后一个元素会报下标越界问题,,并且这样删除,效率低
//错误代码,没办法删除
//        for (PmsBaseAttrInfo pmsBaseAttrInfo : pmsBaseAttrInfos) {
//            List<PmsBaseAttrValue> attrValueList = pmsBaseAttrInfo.getAttrValueList();
//            for (PmsBaseAttrValue pmsBaseAttrValue : attrValueList) {
//                String valueId = pmsBaseAttrValue.getId();
//                for (String delValueId : delValueIds) {
//                    if (valueId.equals(delValueId)){
//                        //删除当前valueId所在的属性组
//                    }
//                }
//            }
//        }


        //当前请求的url的参数就是pmsSearchParam是所提交的参数
        String urlParam = getUrlParam(pmsSearchParam);
        modelMap.put("urlParam", urlParam);//需要做的就是计算出这个urlParam

        String keyword = pmsSearchParam.getKeyword();
        if (StringUtils.isNotBlank(keyword)) {
            modelMap.put("keyword", keyword);
        }

//        //面包屑:用户所点击过的平台属性
//        //面包屑请求 :当前请求 – 当前面包屑的属性 = 新url
//
//        List<PmsSearchCrumb> pmsSearchCrumbs = new ArrayList<>();
//        if (delValueIds != null) {
//            //如果delValueIds参数不为空,说明当前请求中包含属性的参数,每一个属性参数都会生成一个面包屑
//            for (String delValueId : delValueIds) {
//                //生成面包屑的参数
//                PmsSearchCrumb pmsSearchCrumb = new PmsSearchCrumb();
//                pmsSearchCrumb.setValueId(delValueId);
//                pmsSearchCrumb.setValueName(delValueId);
//                pmsSearchCrumb.setUrlParam(getUrlParamForCrumb(pmsSearchParam, delValueId));
//                pmsSearchCrumbs.add(pmsSearchCrumb);
//            }
//        }
//
//        modelMap.put("attrValueSelectedList", pmsSearchCrumbs);

/**
 * 与页面对接
 <a class="select-attr"   th:each="baseAttrValueSelected:${attrValueSelectedList}"
 th:href="'list.html?'+${baseAttrValueSelected.urlParam}"   th:utext=" ${baseAttrValueSelected.valueName} +'<b> ✖ </b>'"  > 2G<b> ✖ </b>
   <!-- 这里是面包屑的显示部分 -->

 <li  th:each="attrValue:${attrInfo.attrValueList}">
 <a th:href="'/list.html?'+${urlParam}+'&valueId='+${attrValue.id}"  th:text="${attrValue.valueName}">属性值</a></li>
 <!-- // 当点击属性列表后=属性列表url是当前请求+被点击的属性列表的新请求
 urlParam是当前请求     &valueId='+${attrValue.id}是属性列表的值   当前请求的url的参数就是pmsSearchParam是所提交的参数 -->


 */

        return "list";
    }

    //单独为面包屑写一个方法
    //返回面包屑的地址参数--给定查询参数和要删除的属性Id,因为要删除这个面包屑,因此这个地址参数要加上这个属性Id=
    private String getUrlParamForCrumb(PmsSearchParam pmsSearchParam, String delValueId) {

        String keyword = pmsSearchParam.getKeyword();
        String catalog3Id = pmsSearchParam.getCatalog3Id();
        String[] skuAttrValueList = pmsSearchParam.getValueId();

        String urlParam = "";
        //拼接关键字和三级分类ID,通过&连接
        if (StringUtils.isNotBlank(keyword)) {
            if (StringUtils.isNotBlank(urlParam)) {
                urlParam = urlParam + "&";
            }
            urlParam += "keyword=" + keyword;
        }
        if (StringUtils.isNotBlank(catalog3Id)) {
            if (StringUtils.isNotBlank(urlParam)) {
                urlParam = urlParam + "&";
            }
            urlParam = urlParam + "catalog3Id=" + catalog3Id;
        }
        if (skuAttrValueList != null) {
            for (String pmsSkuAttrValue : skuAttrValueList) {
                if (!pmsSkuAttrValue.equals(delValueId)) { //面包屑功能。。
                    urlParam = urlParam + "&valueId=" + pmsSkuAttrValue;
                }
            }

        }

        return urlParam;
    }
    //给定传给的搜索参数,返回新请求的url
    private String getUrlParam(PmsSearchParam pmsSearchParam, String... delValueId) {//可变形参。合并两个方法

        String keyword = pmsSearchParam.getKeyword();
        String catalog3Id = pmsSearchParam.getCatalog3Id();
        String[] skuAttrValueList = pmsSearchParam.getValueId();

        String urlParam = "";

        if (StringUtils.isNotBlank(keyword)) {
            if (StringUtils.isNotBlank(urlParam)) {
                urlParam = urlParam + "&";
            }
            urlParam += "keyword=" + keyword;
        }
        if (StringUtils.isNotBlank(catalog3Id)) {
            if (StringUtils.isNotBlank(urlParam)) {
                urlParam = urlParam + "&";
            }
            urlParam = urlParam + "catalog3Id=" + catalog3Id;
        }
        //将所有获取到的属性Id都拼接起来
        if (skuAttrValueList != null) {
            for (String pmsSkuAttrValue : skuAttrValueList) {
                urlParam = urlParam + "&valueId=" + pmsSkuAttrValue;
            }

        }

        return urlParam;
    }

    @RequestMapping("index")
    //这里在搜索的首页,如果用户主动登录
    @LoginRequired(loginSuccess = false)//如果购物车及以后需要对用户的身份进行验证或者验证token都可以使用这个注解
    public String index() {
        return "index";
    }
}

不想写了,写注释了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
gulimall_pms 商品 drop table if exists pms_attr; drop table if exists pms_attr_attrgroup_relation; drop table if exists pms_attr_group; drop table if exists pms_brand; drop table if exists pms_category; drop table if exists pms_category_brand_relation; drop table if exists pms_comment_replay; drop table if exists pms_product_attr_value; drop table if exists pms_sku_images; drop table if exists pms_sku_info; drop table if exists pms_sku_sale_attr_value; drop table if exists pms_spu_comment; drop table if exists pms_spu_images; drop table if exists pms_spu_info; drop table if exists pms_spu_info_desc; /*==============================================================*/ /* Table: pms_attr */ /*==============================================================*/ create table pms_attr ( attr_id bigint not null auto_increment comment '属性id', attr_name char(30) comment '属性名', search_type tinyint comment '是否需要检索[0-不需要,1-需要]', icon varchar(255) comment '属性图标', value_select char(255) comment '可选值列表[用逗号分隔]', attr_type tinyint comment '属性类型[0-销售属性,1-基本属性,2-既是销售属性又是基本属性]', enable bigint comment '启用状态[0 - 禁用,1 - 启用]', catelog_id bigint comment '所属分类', show_desc tinyint comment '快速展示【是否展示在介绍上;0-否 1-是】,在sku中仍然可以调整', primary key (attr_id) ); alter table pms_attr comment '商品属性'; /*==============================================================*/ /* Table: pms_attr_attrgroup_relation */ /*==============================================================*/ create table pms_attr_attrgroup_relation ( id bigint not null auto_increment comment 'id', attr_id bigint comment '属性id', attr_group_id bigint comment '属性分组id', attr_sort int comment '属性组内排序', primary key (id) ); alter table pms_attr_attrgroup_relation comment '属性&

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值