ElasticSearch入门学习笔记

ElasticSearch

简介

Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。

Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它能很方便的使大量数据具有搜索、分析和探索的能力。充分利用Elasticsearch的水平伸缩性,能使数据在生产环境变得更有价值。Elasticsearch 的实现原理主要分为以下几个步骤,首先用户将数据提交到Elasticsearch 数据库中,再通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据,当用户搜索数据时候,再根据权重将结果排名,打分,再将返回结果呈现给用户。

Elasticsearch是与名为Logstash的数据收集和日志解析引擎以及名为Kibana的分析和可视化平台一起开发的。这三个产品被设计成一个集成解决方案,称为“Elastic Stack”(以前称为“ELK stack”)。

ElasticSearch和solr的区别

它们都是基于Lucene搜索服务器基础之上开发的,一款优秀的,高性能的企业级搜索服务器【他们都是基于分词技术构建倒排索引的方式进行查询】

区别:

  1. 当实时建立索引的时候,solr会产生IO阻塞,而es则不会,所以,es查询性能要高于solr
  2. 在不断动态添加数据的时候,solr的检索效率会变的底下,而es则没有什么变化
  3. solr利用zookeeper进行分布式管理,而es自身带有分布式系统管理功能。solr一般部署到web服务器上,比如tomcat,【solr的本质是一个动态web项目
  4. solr支持更多的格式数据【xml、json、csv】,而es仅支持json文件格式
  5. solr是传统搜索应用的有力解决方案,但是es更适合用于新兴的实时搜索应用
  6. 单纯对已有数据进行检索的时候,solr效率高,但实时搜索的时候es效率高
  7. solr官网提供的功能更多,而es本身更注重于核心功能,高级功能有多个第三方插件

ElasticSearch安装

jdk8是最低要求!

Java开发,ElasticSearch 的版本和我们之后对应的 Java 核心jar包,版本对应!

了解目录

bin 启动目录
config 配置目录
	log4j2 日志配置文件
	jvm.options Java 虚拟机的相关配置
	elasticsearch.yml  elasticsearch的配置文件!默认端口9200
lib     相关jar包
logs    日志
modules 功能提供
plugins 插件

启动:双击bin下elasticsearch.bat

访问:http://127.0.0.1:9200

安装可视化界面

地址:https://github.com/mobz/elasticsearch-head

cmd进入解压后的目录,执行 cnpm install,安装依赖,再输入npm run start启动。

连接测试发现存在跨域问题:配置elasticsearch.yml

http.cors.enabled: true
http.cors.allow-origin: "*"

重启es服务,再次连接

初学:可把es当作一个数据库(可以建立索引(库),文档(库中的数据))

这个 head 我们就把它当作数据展示工具,后面的所有查询,用 Kibana。

了解ELK:

ELK是三个开源软件的缩写,分别表示:Elasticsearch , Logstash, Kibana , 它们都是开源软件。新增了一个FileBeat,它是一个轻量级的日志收集处理工具(Agent),Filebeat占用资源少,适合于在各个服务器上搜集日志后传输给Logstash,官方也推荐此工具。

Elasticsearch是个开源分布式搜索引擎,提供搜集、分析、存储数据三大功能。它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。

Logstash 主要是用来日志的搜集、分析、过滤日志的工具,支持大量的数据获取方式。一般工作方式为c/s架构,client端安装在需要收集日志的主机上,server端负责将收到的各节点日志进行过滤、修改等操作在一并发往elasticsearch上去。

Kibana 也是一个开源和免费的工具,Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。

安装Kibana

Kibana是一个针对ElasticSearch的开源分析及可视化平台,用来搜索,查看交互存储在ElasticSearch索引中的数据,使用Kibana,可以通过各种图表进行高级数据分析及展示,Kibana让海量数据更容易展示, 它操作简单,基于浏览器的用户界面可以快速创建仪表板,实时显示ElasticSearch查询动态,设置Kibana非常简单,无需编码或额外的基础架构,几分钟可完成Kibana安装并启动ElasticSearch索引监控。

下载地址:https://www.elastic.co/cn/downloads/kibana

汉化:向config下的 kibana.yml 中添加 i18n.locale: "zh-CN",(汉化包在x-pack下面)。

ES核心概念

elasticsearch是面向文档的,关系数据库和 elasticsearch 对比:

Relational DB ElasticSearch
数据库(database) 索引(index)
表(table) types(慢慢被弃用)
行(row) documents
字段(columns) fileds

elasticsearch(集群)中可以包含多个索引(数据库),每个索引中可以包含多个类型(表),每个类型下可以包含多个文档(行),每个文档下又包含多个字段(列)。

物理设计:elasticsearch在后台把每个索引划分成多个分片,每个分片可以在集群中的不同服务器之间迁移。

文档:

elasticsearch是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,elasticsearch中,文档有几个重要属性:

  • 自我包含,一篇文档同时包含字段和对应的值, 也就是同时包含 key,value。
  • 可以是层次型的,一个文档中包含文档,复杂的逻辑实现就是这么来的。
  • 灵活的结构,文档不依赖预先定义的模式,关系型数据库中,要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的新增一个字段。

尽管我们可以随意的新增或忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串,也可以是整型,因为elasticsearch会保存字段和类型之间的映射,这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候称为映射类型。

类型:

类型是文档的逻辑容器,就像关系型数据库一样,表是行的容器,类型中对于字段的定义称为映射,比如name映射为字符串类型。我们说文档是无模式的,它不需要拥有映射中定义的所有字段,比如新增一个字段,那么elasticsearch是怎么做到的?elasticsearch会自动将字段加入映射,但是这个字段不确定它是什么类型,elasticsearch就会开始猜测,如果这个值是18,那么elasticsearch就会认为它是整型,但是elasticsearch也可能会猜不对,所以最安全的方式就是提前定义好需要的映射,这就和关系数据库殊途同归了,先定义好字段再使用。

索引:

索引是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合,索引存储了映射类型的字段和其它设置,然后它们被存储到各个分片上了。

elasticsearch采用了倒排索引的结构,采用lucene索引作为底层,这种结构适用于快速的全文检索。

在elasticsearch中,索引这个词被频繁使用,这是术语。在elasticsearch中,索引被分为多个分片,每份分片是一个Lucene的索引,所以一个elasticsearch索引是由多个Lucene索引组成的。

IK分词器插件

分词:把一段文字划分成一个个的关键字,我们在搜索时会把自己的信息进行分词,把数据库或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词。

如果是中文,建议使用IK分词器。

IK提供了两个分词算法:ik_smart 和 ik_max_word,其中 ik_smart为最少切分,ik_max_word为最细粒度划分。

下载安装:https://github.com/medcl/elasticsearch-analysis-ik,下载完毕后解压放到 elasticsearch 插件中即可(新建ik目录)。

cmd进入bin目录下,输入elasticsearch-plugin list 查看插件

使用kibana测试,使用不用的分词器的效果:

ik_smart最少切分

ik_max_word,穷尽词库的可能。

自己添加字典,在ik的config中的IKAnalyzer.cfg.xml添加zr.dic。

测试未配置自己的分词器前:

配置自己的分词器后:(重启es)

以后需要自己配置分词,就这样在自己的dic文件中配置即可。

Rest风格说明

一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件,它主要用于客户端和服务端交互类的软件,基于这个风格设计的软件可以更加简洁,更有层次,更易于实现缓存等机制。

基本Rest命令说明:

method url地址 描述
put localhost:9200/索引名称/类型名称/文档id 创建文档(指定文档id)
post localhost:9200/索引名称/类型名称 创建文档(指定文档id)
post localhost:9200/索引名称/类型名称/文档id/_update 修改文档
delete localhost:9200/索引名称/类型名称/文档id 删除文档
get localhost:9200/索引名称/类型名称/文档id 查询文档通过文档id
post localhost:9200/索引名称/类型名称/_search 查询所有数据

索引的基本操作

测试:

创建一个索引,

put /索引名/类型名/文档id

{请求体}

执行后自动增加索引,数据也自动添加了。

数据类型:

  • 字符串类型

    text,keyword

  • 数值类型

    long,integer,short,byte,double,float,half float,scaled float

  • 日期类型

    date

  • 布尔值类型

    boolean

  • 二进制类型

    binary

  • .......

指定字段的类型:创建规则

通过get请求,查看具体的规则信息

查看默认的信息

查看

如果自己的文档没有指定,es就会给我们默认配置字段类型。

拓展:通过 get _cat/*** 可以获得es的很多信息。

修改,可使用put提交覆盖。

使用post方式进行修改

删除索引

通过delete命令实现删除,根据请求判断是删除索引还是删除文档记录。

文档的基本操作

基本操作

添加数据:

PUT /zr/user/1
{
  "name": "周周",
  "age": "18",
  "desc": "好好学习",
  "tags": ["java","直男"]
}

查看

获取数据

更新数据 PUT

更新数据 POST _update (推荐使用这种方式修改)

简单的搜索

GET zr/user/1

简单的条件查询,可根据默认的映射规则,产生基本的查询。

复杂操作(排序,分页,高亮,模糊查询,精准查询)

显示想看见的

后面用Java操作es,所有的方法和对象就是这里面的key。

排序

GET zr/user/_search
{
  "query": {
    "match": {
      "name": "周"
    }
  },
  "sort": [
    {
      "age": {
        "order": "asc"
      }
    }
  ]
}

分页 form:开始位置,size:返回数据大小

布尔值查询,must:所有的条件都符合

should

must_not

gt:大于,gte:大于等于。lt:小于,lte:小于等于

匹配多个条件

精确查询

term查询是直接通过倒排索引指定的词条进行精确查找的。

关于分词:

term:直接查询精确的

match:会使用分析器解析(先分析文档再查询)

两个类型:

拆分

多个值匹配的精确查询

高亮查询

集成SpringBoot

官方文档:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

找到原生依赖:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.11.1</version>
</dependency>

初始化:

分析这个类中的方法即可!

配置基本的项目

保证版本一致

获取对象

package com.zr.zresapi.config;
//找对象
//放到spring中待用

@Configuration
public class ElasticSearchClientConfig {
    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("127.0.0.1", 9200, "http")));
        return client;
    }
}

实体类

package com.zr.zresapi.pojo;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class User {
    private String name;
    private int age;
}

具体的api测试

1.创建索引

2.判断索引是否存在

3.删除索引

4.创建文档

5.crud文档

package com.zr.zresapi;

@SpringBootTest
class ZrEsApiApplicationTests {
    //面向对象来操作
    @Autowired
    @Qualifier("restHighLevelClient")
    private RestHighLevelClient client;

    //测试索引的创建 Request
    @Test
    void testCreateIndex() throws IOException {
        //创建索引请求
        CreateIndexRequest request = new CreateIndexRequest("zhour_index");
        //客户端执行创建请求  indicesClient,请求后获得响应
        CreateIndexResponse createIndexResponse =
                client.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(createIndexResponse);
    }

    //测试获取索引,是否存在
    @Test
    void testExitsIndex() throws IOException {
        GetIndexRequest request = new GetIndexRequest("zhour_index");
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);

    }

    //测试删除索引
    @Test
    void testDeleteIndex() throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest("zhour_index");
        AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
        System.out.println(delete.isAcknowledged());

    }

    //测试添加文档
    @Test
    void testAddDocument() throws IOException {
        //创建对象
        User user = new User("周周",18);
        //创建请求
        IndexRequest request = new IndexRequest("zhour_index");
        //规则  put /zhour_index/_doc/1
        request.id("1");
        request.timeout(TimeValue.timeValueSeconds(1));
        request.timeout("1s");

        //将数据放入请求  json
        request.source(JSON.toJSONString(user), XContentType.JSON);

        //客户端发送请求,获取响应回来的结果
        IndexResponse index = client.index(request, RequestOptions.DEFAULT);

        System.out.println(index.toString());
        System.out.println(index.status());  //对应命令返回的状态

    }

    //获取文档 判断是否存在
    @Test
    void testIsExists() throws IOException {
        GetRequest request = new GetRequest("zhour_index", "1");
        //不获取返回的 _source 的上下文了
        request.fetchSourceContext(new FetchSourceContext(false));
        request.storedFields("_none");

        boolean exists = client.exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);  //返回全部的内容
    }

    //获取文档的信息
    @Test
    void testGetDocument() throws IOException {
        GetRequest request = new GetRequest("zhour_index", "1");
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        System.out.println(response.getSourceAsString());  //打印文档的内容
        System.out.println(response);
    }

    //更新文档的信息
    @Test
    void testUpdateDocument() throws IOException {
        UpdateRequest request = new UpdateRequest("zhour_index", "1");
        request.timeout("1s");
        User user = new User("周周8888", 20);
        request.doc(JSON.toJSONString(user),XContentType.JSON);

        UpdateResponse update = client.update(request, RequestOptions.DEFAULT);
        System.out.println(update.status());
        System.out.println(update.toString());
    }

    //删除文档
    @Test
    void testDeleteDocument() throws IOException {
        DeleteRequest request = new DeleteRequest("zhour_index", "1");
        request.timeout("1s");
        DeleteResponse delete = client.delete(request, RequestOptions.DEFAULT);
        System.out.println(delete.status());
    }

    //批量插入数据
    @Test
    void testBulkRequest() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("6s");

        ArrayList<User> userList = new ArrayList<>();
        userList.add(new User("周周1",8));
        userList.add(new User("周周2",15));
        userList.add(new User("周周3",18));
        userList.add(new User("周周4",22));

        //批处理请求
        for (int i = 0; i < userList.size(); i++) {
            bulkRequest.add(
                    new IndexRequest("zhour_index")
                    .id(""+(i+1))  //不设置是默认的随机id
                    .source(JSON.toJSONString(userList.get(i)),XContentType.JSON)
            );
        }
        BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulk.status());
        System.out.println(bulk.hasFailures());  //是否失败,false代表成功
    }

    //查询
    //searchRequest 搜索请求
    //SearchSourceBuilder 条件构造
    //HightLightBuilder 构造高亮
    //TermQueryBuilder  精确查询
    //*** QueryBuilder  对应其它命令
    @Test
    void testSearch() throws IOException {
        SearchRequest searchRequest = new SearchRequest("zhour_index");
        //构建搜索的条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //查询条件  使用QueryBuilders 工具类来实现
        //精确查询
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "周");
        //查询全部
        //MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();

        sourceBuilder.query(termQueryBuilder);
        //分页
        //sourceBuilder.from();
        //sourceBuilder.size();
        sourceBuilder.timeout(new TimeValue(10, TimeUnit.SECONDS));
        searchRequest.source(sourceBuilder);
        SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println(JSON.toJSONString(search.getHits()));
        System.out.println("====================");
        for (SearchHit hit : search.getHits().getHits()) {
            System.out.println(hit.getSourceAsMap());
        }
    }
}

实战

爬虫,前后端分离,搜索高亮

爬取数据:获取请求返回的页面信息,筛选出我们需要的数据。

引入jsoup包。

在elasticsearch中创建索引,jd_goods。

项目结构目录

application.properties

server.port=8080
#关闭thymeleaf的缓存
spring.thymeleaf.cache=false

实体类

package com.zr.pojo;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Content {

    private String name;
    private String img;
    private String price;
}

工具类,可直接运行main函数,查看爬取的 img 地址,价格,名称。

package com.zr.utils;

@Component
public class HtmlParseUtil {
    public static void main(String[] args) throws IOException {
        new HtmlParseUtil().parseJD("java").forEach(System.out::println);

    }
    public List<Content> parseJD(String keywords) throws IOException {
        //获得请求  https://search.jd.com/Search?keyword=java
        //前提 联网   中文搜索 +"&enc=utf-8"
        String url = "https://search.jd.com/Search?keyword="+keywords;

        //解析网页,jsoup返回的document就是document对象
        Document document = Jsoup.parse(new URL(url), 30000);
        //所有在js中使用的方法,这里都可以使用
        Element element = document.getElementById("J_goodsList");
        //System.out.println(element.html());
        //获取所有的li标签
        Elements elements = element.getElementsByTag("li");

        ArrayList<Content> list = new ArrayList<>();

        //获取元素中的内容 el就是一个li标签了
        for (Element el : elements) {
            //关于这种图片,很多网站都是懒加载的
            // String img = el.getElementsByTag("img").eq(0).attr("src");
            String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
            String price = el.getElementsByClass("p-price").eq(0).text();
            String name = el.getElementsByClass("p-name").eq(0).text();
            // System.out.println("======================");
            // System.out.println(img);
            // System.out.println(price);
            // System.out.println(name);

            Content content = new Content();
            content.setImg(img);
            content.setName(name);
            content.setPrice(price );

            list.add(content);
        }
        return list;
    }
}

ElasticSearchClientConfig

package com.zr.config;
//找对象
//放到spring中待用

@Configuration
public class ElasticSearchClientConfig {
    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("127.0.0.1", 9200, "http")));
        return client;
    }
}

具体业务实现

package com.zr.service;

//业务编写
@Service
public class ContentService {
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    //解析数据 放入es索引中
    public Boolean parseContent(String keywords) throws IOException {
        List<Content> contents = new HtmlParseUtil().parseJD(keywords);

        //把查询的数据放入es中
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("2m");
        for (int i = 0; i < contents.size(); i++) {
            bulkRequest.add(
                    new IndexRequest("jd_goods")
                            .source(JSON.toJSONString(contents.get(i)),XContentType.JSON));
        }
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        return bulk.hasFailures();
    }

    //获取数据,实现搜索功能
    public List<Map<String,Object>> searchPage(String keyword,int pageNo,int pageSize) throws IOException {
        //条件搜索
        SearchRequest searchRequest = new SearchRequest("jd_goods");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //精准匹配
        TermQueryBuilder query = QueryBuilders.termQuery("name", keyword);
        sourceBuilder.query(query);
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        //分页
        sourceBuilder.from(pageNo);
        sourceBuilder.size(pageSize);
        //执行搜索
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //解析结果
        ArrayList<Map<String,Object>> list = new ArrayList<>();
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            list.add(hit.getSourceAsMap());
        }
        return list;
    }

    //获取数据,实现高亮搜索功能
    public List<Map<String,Object>> searchHighPage(String keyword,int pageNo,int pageSize) throws IOException {
        //条件搜索
        SearchRequest searchRequest = new SearchRequest("jd_goods");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //精准匹配
        TermQueryBuilder query = QueryBuilders.termQuery("name", keyword);
        sourceBuilder.query(query);
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        //分页
        sourceBuilder.from(pageNo);
        sourceBuilder.size(pageSize);

        //高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name");
        highlightBuilder.requireFieldMatch(false);  //关闭多个高亮显示
        highlightBuilder.preTags("<span style='color:red'>");
        highlightBuilder.postTags("</span>");
        sourceBuilder.highlighter(highlightBuilder);

        //执行搜索
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //解析结果
        ArrayList<Map<String,Object>> list = new ArrayList<>();
        for (SearchHit hit : searchResponse.getHits().getHits()) {

            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            HighlightField name = highlightFields.get("name");
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();  //原来的结果
            //解析高亮的字段  将原来的字段换为高亮的字段
            if (name!=null){
                Text[] fragments = name.fragments();
                String n_name = "";
                for (Text text : fragments) {
                    n_name += text;
                }
                sourceAsMap.put("name",n_name);  //替换高亮的字段
            }
            list.add(sourceAsMap);
        }
        return list;
    }
}

ContentController

package com.zr.controller;

import com.zr.service.ContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.List;
import java.util.Map;

@RestController
public class ContentController {

    @Autowired
    private ContentService contentService;

    @GetMapping("/parse/{keyword}")
    public Boolean parse(@PathVariable("keyword") String keywords) throws IOException {
        return contentService.parseContent(keywords);
    }

    @GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
    public List<Map<String,Object>> search(
            @PathVariable("keyword") String keyword,
            @PathVariable("pageNo") int pageNo,
            @PathVariable("pageSize") int pageSize) throws IOException {
        // return contentService.searchPage(keyword, pageNo, pageSize); //普通查询
        //高亮
        return contentService.searchHighPage(keyword, pageNo, pageSize);
    }
}

index.html(需要引入axios.js, vue.js, jQuery.js, css,京东logo图片)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="utf-8"/>
    <title>京东</title>
    <link rel="stylesheet" th:href="@{/css/style.css}"/>
</head>

<body class="pg">
<div class="page" id="app">
    <div id="mallPage" class=" mallist tmall- page-not-market ">

        <!-- 头部搜索 -->
        <div id="header" class=" header-list-app">
            <div class="headerLayout">
                <div class="headerCon ">
                    <!-- Logo -->
                    <h1 id="mallLogo">
                        <img th:src="@{/images/jdlogo.png}" alt="">
                    </h1>

                    <div class="header-extra">

                        <!-- 搜索 -->
                        <div id="mallSearch" class="mall-search">
                            <form name="searchTop" class="mallSearch-form clearfix">
                                <fieldset>
                                    <legend>天猫搜索</legend>
                                    <div class="mallSearch-input clearfix">
                                        <div class="s-combobox" id="s-combobox-685">
                                            <div class="s-combobox-input-wrap">
                                                <input v-model="keyword" type="text" autocomplete="off" value="java"
                                                       id="mq"
                                                       class="s-combobox-input" aria-haspopup="true"
                                                        >
                                            </div>
                                        </div>
                                        <button type="submit" @click.prevent="searchKey" id="searchbtn">搜索</button>
                                    </div>
                                </fieldset>
                            </form>
                            <ul class="relKeyTop">
                                <li><a>Java</a></li>
                                <li><a>Spring</a></li>
                                <li><a>Spring Boot</a></li>
                                <li><a>Spring Cloud</a></li>
                                <li><a>MySQL</a></li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- 商品详情页面 -->
        <div id="content">
            <div class="main">
                <!-- 品牌分类 -->
                <form class="navAttrsForm">
                    <div class="attrs j_NavAttrs" style="display:block">
                        <div class="brandAttr j_nav_brand">
                            <div class="j_Brand attr">
                                <div class="attrKey">
                                    品牌
                                </div>
                                <div class="attrValues">
                                    <ul class="av-collapse row-2">
                                        <li><a href="#"> MacBook Pro </a></li>
                                        <li><a href="#"> iPad Pro </a></li>
                                        <li><a href="#"> iPhone Pro </a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                </form>

                <!-- 排序规则 -->
                <div class="filter clearfix">
                    <a class="fSort fSort-cur">综合<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">人气<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">新品<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">销量<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">价格<i class="f-ico-triangle-mt"></i><i class="f-ico-triangle-mb"></i></a>
                </div>

                <!-- 商品详情 -->
                <div class="view grid-nosku">

                    <div class="product" v-for="result in results">
                        <div class="product-iWrap">
                            <!-- 商品封面 -->
                            <div class="productImg-wrap">
                                <a class="productImg">
                                    <img :src="result.img">
                                </a>
                            </div>
                            <!-- 价格 -->
                            <p class="productPrice">
                                <em>{{result.price}}</em>
                            </p>
                            <!-- 标题 -->
                            <p class="productTitle">
                                <a v-html="result.name"></a>
                            </p>
                            <!-- 评价数量 -->
                            <p class="productCommit">
                                <span>1.2w+条评价</span>
                            </p>
                            <!-- 出版社 -->
                            <div class="productShop">
                                <span> 小周书城 </span>
                            </div>
                            <!-- 成交信息 -->
                            <p class="productStatus">
                                <span>月成交 <em>999笔</em></span>
                                <span>评价 <a>3</a></span>
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<!--前端使用vue,实现前后端分离-->

<script th:src="@{/js/axios.min.js}"></script>
<script th:src="@{/js/vue.min.js}"></script>
<script>
    new Vue({
        el: '#app',
        data: {
            keyword: '',
            results: []
        },
        methods: {
            searchKey(){
                var keyword = this.keyword;
                console.log(keyword);
                //对接后端的接口
                axios.get('search/'+keyword+'/1/10').then(response=>{
                    console.log(response.data);
                    this.results = response.data;
                })
            }
        }
    });
</script>
</body>
</html>

浏览器测试搜索和高亮:

原文地址:https://www.cnblogs.com/zhou-zr/p/14698647.html