springboot项目整合Elasticsearch

Elasticsearch 的几种Java客户端对比

客户端优点缺点说明
TransportClient使用Transport 接口进行通信,能够使用ES集群中的一些特性,性能最好7.x 已废弃,8.x直接删除了使用端口 9300
Java Low Level Rest Client与ES版本之间没有关系,适用于作为所有版本ES的客户端用户需自己编组请求JSON串,及解析响应JSON串通过http与集群交互,使用端口 9200
Java High Level Rest Client基于低级别的REST客户端,增加了编组请求JSON串、解析响应JSON串等相关api使用的版本需要保持和ES服务端的版本一致,否则会有版本问题基于Low Level Rest Client,使用端口 9200;已于 7.15.0 版本(2021年09月22日)废弃
Java API客户端构建者模式增强了客户端代码的可用性和可读性,写法更简洁7.16版本之后才可使用7.16 版本(2021年12月8日)推出 Elasticsearch Java API Client

基于以上优缺点以及elasticsearch服务端版本(博主版本是6.8),博主决定采用Java High Level Rest Client方式连接

项目代码说明

项目代码可以参考该工程的elasticsearch-service模块
spring-boot-starter-parent 版本 2.1.7.RELEASE
elasticsearch-rest-high-level-client版本 6.4.3

引入依赖

	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
        </dependency>
    </dependencies>

增加配置

server:
  port: 9528

spring:
  application:
    name: elasticsearch-service

elasticsearch:
  schema: http
  address: 192.168.220.125:9200
  connectTimeout: 5000
  socketTimeout: 5000
  connectionRequestTimeout: 5000
  maxConnectNum: 100
  maxConnectPerRoute: 100

定义配置类

package org.example.elasticsearch.configuration;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class ElasticSearchConfiguration {
    /** 协议 */
    @Value("${elasticsearch.schema:http}")
    private String schema;

    /** 集群地址,如果有多个用“,”隔开 */
    @Value("${elasticsearch.address}")
    private String address;

    /** 连接超时时间 */
    @Value("${elasticsearch.connectTimeout}")
    private int connectTimeout;

    /** Socket 连接超时时间 */
    @Value("${elasticsearch.socketTimeout}")
    private int socketTimeout;

    /** 获取连接的超时时间 */
    @Value("${elasticsearch.connectionRequestTimeout}")
    private int connectionRequestTimeout;

    /** 最大连接数 */
    @Value("${elasticsearch.maxConnectNum}")
    private int maxConnectNum;

    /** 最大路由连接数 */
    @Value("${elasticsearch.maxConnectPerRoute}")
    private int maxConnectPerRoute;

    @Bean(name = "restHighLevelClient")
    public RestHighLevelClient restHighLevelClient() {
        // 拆分地址
        List<HttpHost> hostLists = new ArrayList<>();
        String[] hostList = address.split(",");
        for (String addr : hostList) {
            String host = addr.split(":")[0];
            String port = addr.split(":")[1];
            hostLists.add(new HttpHost(host, Integer.parseInt(port), schema));
        }
        // 转换成 HttpHost 数组
        HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});
        // 构建连接对象
        RestClientBuilder builder = RestClient.builder(httpHost);
        // 异步连接延时配置
        builder.setRequestConfigCallback(requestConfigBuilder -> {
            requestConfigBuilder.setConnectTimeout(connectTimeout);
            requestConfigBuilder.setSocketTimeout(socketTimeout);
            requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeout);
            return requestConfigBuilder;
        });
        // 异步连接数配置
        builder.setHttpClientConfigCallback(httpClientBuilder -> {
            httpClientBuilder.setMaxConnTotal(maxConnectNum);
            httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute);
            return httpClientBuilder;
        });
        return new RestHighLevelClient(builder);
    }
}

定义实体类、service 及其实现类

实体类

package org.example.elasticsearch.entity;

import java.io.Serializable;
import java.util.List;

public class Employee implements Serializable {
    private String first_name;
    private String last_name;
    private int age;
    private String about;
    private List<String> interests;

    @Override
    public String toString() {
        return "Employee{" +
                "first_name='" + first_name + '\'' +
                ", last_name='" + last_name + '\'' +
                ", age=" + age +
                ", about='" + about + '\'' +
                ", interests=" + interests +
                '}';
    }

    public String getFirst_name() {
        return first_name;
    }

    public void setFirst_name(String first_name) {
        this.first_name = first_name;
    }

    public String getLast_name() {
        return last_name;
    }

    public void setLast_name(String last_name) {
        this.last_name = last_name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAbout() {
        return about;
    }

    public void setAbout(String about) {
        this.about = about;
    }

    public List<String> getInterests() {
        return interests;
    }

    public void setInterests(List<String> interests) {
        this.interests = interests;
    }
}

service类

package org.example.elasticsearch.service;

import org.example.elasticsearch.entity.Employee;

public interface EsEmployeeService {
    /**
     * 增加文档信息
     */
    public void addDocument();

    /**
     * 获取文档信息
     */
    public Employee getDocument();

    /**
     * 更新文档信息
     */
    public void updateDocument();

    /**
     * 删除文档信息
     */
    public void deleteDocument();
}

实现类

package org.example.elasticsearch.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.example.elasticsearch.entity.Employee;
import org.example.elasticsearch.service.EsEmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Arrays;

@Service
public class EsEmployeeServiceImpl implements EsEmployeeService {
    private RestHighLevelClient restHighLevelClient;

    private static final String INDEX_NAME = "megacorp";
    private static final String TYPE_NAME = "employee";
    @Autowired
    public EsEmployeeServiceImpl(RestHighLevelClient restHighLevelClient) {
        this.restHighLevelClient = restHighLevelClient;
    }

    @Override
    public void addDocument() {
        IndexRequest request = new IndexRequest(INDEX_NAME, TYPE_NAME, "4");
        Employee employee = new Employee();
        employee.setFirst_name("codefarmer");
        employee.setLast_name("008");
        employee.setAge(20);
        employee.setAbout("i like football");
        employee.setInterests(Arrays.asList("football","eat"));
        // 将对象转换为 byte 数组
        ObjectMapper mapper = new ObjectMapper();
        try {
            byte[] json = mapper.writeValueAsBytes(employee);
            // 设置文档内容
            request.source(json, XContentType.JSON);
            IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
            System.out.println(response.status());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Employee getDocument() {
        GetRequest request = new GetRequest(INDEX_NAME, TYPE_NAME, "4");
        try {
            GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
            if(response.isExists()){
                ObjectMapper mapper = new ObjectMapper();
                Employee employee = mapper.readValue(response.getSourceAsBytes(), Employee.class);
                System.out.println(employee);
                return employee;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void updateDocument() {
        UpdateRequest request = new UpdateRequest(INDEX_NAME, TYPE_NAME, "4");
        try {
            // 设置文档内容
            request.doc("about", "i like basketball");
            UpdateResponse response = restHighLevelClient.update(request, RequestOptions.DEFAULT);
            System.out.println(response.status());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void deleteDocument() {
        DeleteRequest request = new DeleteRequest(INDEX_NAME, TYPE_NAME, "4");
        try {
            DeleteResponse response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
            System.out.println(response.status());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

新增controller类

package org.example.elasticsearch.controller;

import org.example.elasticsearch.entity.Employee;
import org.example.elasticsearch.service.EsEmployeeService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/employee")
public class EmployeeController {
    @Autowired
    private EsEmployeeService esEmployeeService;

    @GetMapping("/es/query")
    public Employee queryEmployee() {
        return esEmployeeService.getDocument();
    }

    @GetMapping("/es/add")
    public String addEmployee() {
        esEmployeeService.addDocument();
        return "success";
    }

    @GetMapping("/es/update")
    public String updateEmployee() {
        esEmployeeService.updateDocument();
        return "success";
    }

    @GetMapping("/es/del")
    public String delEmployee() {
        esEmployeeService.deleteDocument();
        return "success";
    }
}

测试验证

http://localhost:9528/employee/es/add
在这里插入图片描述
kibana查询新增的文档
在这里插入图片描述

仿某东搜索示例

效果图

先看下搜索效果,根据输入的搜索关键字可以搜索到具体商品,并且可以高亮显示关键字
在这里插入图片描述

新增商品实体类

package org.example.elasticsearch.entity;

import java.io.Serializable;
import java.math.BigDecimal;

public class Goods implements Serializable {
    private String name;
    private BigDecimal price;
    private String img;
    private String desc;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public String getImg() {
        return img;
    }

    public void setImg(String img) {
        this.img = img;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

新增service及实现类

package org.example.elasticsearch.service;

import org.example.elasticsearch.entity.Goods;

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

public interface GoodsService {
    boolean batchInsertGoods(List<Goods> goodsList) throws IOException;

    List<Map<String, Object>> queryGoodsByName(String name, int pageNo, int pageSize) throws IOException;
}
package org.example.elasticsearch.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.example.elasticsearch.constant.EsIndexName;
import org.example.elasticsearch.entity.Goods;
import org.example.elasticsearch.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

@Service
public class GoodsServiceImpl implements GoodsService {

    @Autowired
    private RestHighLevelClient client;

    @Override
    public boolean batchInsertGoods(List<Goods> goodsList) throws IOException {
        BulkRequest request = new BulkRequest();
        goodsList.stream().forEach(goods -> {
            IndexRequest indexRequest = new IndexRequest(EsIndexName.GOODS.toString(), "_doc");
            ObjectMapper mapper = new ObjectMapper();
            String json = "";
            try {
                json = mapper.writeValueAsString(goods);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
                return;
            }
            indexRequest.source(json, XContentType.JSON);
            request.add(indexRequest);
        });

        AtomicBoolean isSuccess = new AtomicBoolean(true);
        BulkResponse responses = client.bulk(request, RequestOptions.DEFAULT);
        responses.forEach(item -> {
            System.out.println(item.status().getStatus()+" "+ item.isFailed() +" " + item.getResponse());
            if(item.isFailed()){
                isSuccess.set(false);
            }
        });
        return isSuccess.get();
    }

    @Override
    public List<Map<String, Object>> queryGoodsByName(String name, int pageNo, int pageSize) throws IOException {
        String FIELD = "name";
        SearchRequest searchRequest = new SearchRequest(EsIndexName.GOODS.toString());
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(QueryBuilders.matchQuery(FIELD, name));
        sourceBuilder.from(pageNo);
        sourceBuilder.size(pageSize);
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

        //高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        HighlightBuilder.Field highlightTitle =
                new HighlightBuilder.Field(FIELD);
//        highlightTitle.highlighterType("unified");
        highlightBuilder.field(highlightTitle).preTags("<span style='color:red'>").postTags("</span>");
        sourceBuilder.highlighter(highlightBuilder);

        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        long totalHits = hits.getTotalHits();
        float maxScore = hits.getMaxScore();
        System.out.println("总数及最大分 "+totalHits + " "+ maxScore);
        //用高亮部分的内容替换原source中的内容
        List<Map<String, Object>> ret = new ArrayList<>();
        for (SearchHit hit : hits.getHits()) {
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();

            sourceAsMap.put("desc", sourceAsMap.get(FIELD));//先将name字段的内容放入desc中,方便放入页面元素的title属性
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            HighlightField highlight = highlightFields.get(FIELD);
            Text[] fragments = highlight.fragments();
            String fragmentString = fragments[0].string();
            // do something with the SearchHit
            sourceAsMap.put(FIELD, fragmentString);
            ret.add(sourceAsMap);
        }

        return ret;
    }
}

新增controller

package org.example.elasticsearch.controller;

import org.example.elasticsearch.entity.Goods;
import org.example.elasticsearch.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/goods")
public class GoodsController {
    @Autowired
    private GoodsService goodsService;

    @PostMapping("/addGoods")
    public String addGoods(String name, String img, BigDecimal price) {
        Goods goods = new Goods();
        goods.setName(name);
        goods.setImg(img);
        goods.setPrice(price);

        try {
            goodsService.batchInsertGoods(Collections.singletonList(goods));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "success";
    }

    @GetMapping("/queryGoodsByName")
    public List<Map<String, Object>> queryGoodsByName(String keywords) {

        try {
            List<Map<String, Object>> maps = goodsService.queryGoodsByName(keywords, 0 ,12);
            return maps;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

新增template

插入数据前,需要在kibana中执行以下内容,定义一个模板,这里需要使用ik分词器,否则搜索中文会达不到预期效果

PUT /_template/goods_template
{
  "index_patterns" : ["goods"],
  "settings" : {
      "number_of_shards" : 1,
      "number_of_replicas": 0
  },
  "mappings": {
    "_doc": {
      "properties": {
        "name": {
          "type":  "text",
          "analyzer": "ik_max_word"
        },
        "price": {
          "type": "scaled_float",
          "scaling_factor": 100
        },
        "img": {
          "type": "keyword"
        },
        "desc": {
          "type":  "text",
          "analyzer": "ik_max_word"
        }
      }
    }
  }
}

使用爬虫程序初始化elasticsearch数据

使用爬虫程序获取某东的商品数据,并调用上面controller的addGoods接口,向elasticsearch中插入数据
爬虫程序采用开源项目,读者只需要下载源码本地启动即可运行。
在这里插入图片描述
这里提供该爬虫的配置程序,读者只需要将xml内容复制到一个新的流程图中即可执行(声明:仅用于学习,勿商用)

<mxGraphModel>
  <root>
    <mxCell id="0">
      <JsonProperty as="data">
        {&quot;spiderName&quot;:&quot;京东搜索&quot;,&quot;submit-strategy&quot;:&quot;random&quot;,&quot;threadCount&quot;:&quot;&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="1" parent="0"/>
    <mxCell id="2" value="开始" style="start" parent="1" vertex="1">
      <mxGeometry x="100" y="80" width="32" height="32" as="geometry"/>
      <JsonProperty as="data">
        {&quot;shape&quot;:&quot;start&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="3" value="开始抓取" style="request" parent="1" vertex="1">
      <mxGeometry x="230" y="80" width="32" height="32" as="geometry"/>
      <JsonProperty as="data">
        {&quot;value&quot;:&quot;开始抓取&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;sleep&quot;:&quot;&quot;,&quot;timeout&quot;:&quot;&quot;,&quot;response-charset&quot;:&quot;&quot;,&quot;retryCount&quot;:&quot;&quot;,&quot;retryInterval&quot;:&quot;&quot;,&quot;body-type&quot;:&quot;none&quot;,&quot;body-content-type&quot;:&quot;text/plain&quot;,&quot;loopCount&quot;:&quot;&quot;,&quot;url&quot;:&quot;https://search.jd.com/search?keyword=汉服女&quot;,&quot;proxy&quot;:&quot;&quot;,&quot;request-body&quot;:&quot;&quot;,&quot;follow-redirect&quot;:&quot;1&quot;,&quot;tls-validate&quot;:&quot;1&quot;,&quot;cookie-auto-set&quot;:&quot;1&quot;,&quot;repeat-enable&quot;:&quot;0&quot;,&quot;shape&quot;:&quot;request&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="5" value="" style="strokeWidth=2;sharp=1;" parent="1" source="2" target="3" edge="1">
      <mxGeometry relative="1" as="geometry"/>
      <JsonProperty as="data">
        {&quot;value&quot;:&quot;&quot;,&quot;exception-flow&quot;:&quot;0&quot;,&quot;lineWidth&quot;:&quot;2&quot;,&quot;line-style&quot;:&quot;sharp&quot;,&quot;lineColor&quot;:&quot;black&quot;,&quot;condition&quot;:&quot;&quot;,&quot;transmit-variable&quot;:&quot;1&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="6" value="定义变量" style="variable" parent="1" vertex="1">
      <mxGeometry x="370" y="80" width="32" height="32" as="geometry"/>
      <JsonProperty as="data">
        {&quot;value&quot;:&quot;定义变量&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;variable-name&quot;:[&quot;goodsList&quot;,&quot;goodLi&quot;],&quot;variable-description&quot;:[&quot;&quot;,&quot;&quot;],&quot;loopCount&quot;:&quot;&quot;,&quot;variable-value&quot;:[&quot;${resp.selector(&#39;#J_goodsList&#39;)}&quot;,&quot;${goodsList.selectors(&#39;li.gl-item&#39;)}&quot;],&quot;shape&quot;:&quot;variable&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="7" value="" style="strokeWidth=2;sharp=1;" parent="1" source="3" target="6" edge="1">
      <mxGeometry relative="1" as="geometry"/>
      <JsonProperty as="data">
        {&quot;value&quot;:&quot;&quot;,&quot;exception-flow&quot;:&quot;0&quot;,&quot;lineWidth&quot;:&quot;2&quot;,&quot;line-style&quot;:&quot;sharp&quot;,&quot;lineColor&quot;:&quot;black&quot;,&quot;condition&quot;:&quot;&quot;,&quot;transmit-variable&quot;:&quot;1&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="9" value="循环" style="loop" parent="1" vertex="1">
      <mxGeometry x="476" y="80" width="32" height="32" as="geometry"/>
      <JsonProperty as="data">
        {&quot;value&quot;:&quot;循环&quot;,&quot;loopItem&quot;:&quot;&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;loopCount&quot;:&quot;${goodLi}&quot;,&quot;loopStart&quot;:&quot;0&quot;,&quot;loopEnd&quot;:&quot;-1&quot;,&quot;shape&quot;:&quot;loop&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="11" value="" style="strokeWidth=2;sharp=1;" parent="1" source="6" target="9" edge="1">
      <mxGeometry relative="1" as="geometry"/>
      <JsonProperty as="data">
        {&quot;value&quot;:&quot;&quot;,&quot;exception-flow&quot;:&quot;0&quot;,&quot;lineWidth&quot;:&quot;2&quot;,&quot;line-style&quot;:&quot;sharp&quot;,&quot;lineColor&quot;:&quot;black&quot;,&quot;condition&quot;:&quot;&quot;,&quot;transmit-variable&quot;:&quot;1&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="13" value="定义变量" style="variable" parent="1" vertex="1">
      <mxGeometry x="578" y="80" width="32" height="32" as="geometry"/>
      <JsonProperty as="data">
        {&quot;value&quot;:&quot;定义变量&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;variable-name&quot;:[&quot;name&quot;,&quot;img&quot;,&quot;price&quot;],&quot;variable-description&quot;:[&quot;&quot;,&quot;&quot;,&quot;&quot;],&quot;loopCount&quot;:&quot;&quot;,&quot;variable-value&quot;:[&quot;${item.selector(\&quot;div.p-name a em\&quot;).text()}&quot;,&quot;${item.selector(\&quot;div.p-img img\&quot;).attr(\&quot;data-lazy-img\&quot;)}&quot;,&quot;${item.selector(\&quot;div.p-price i\&quot;).text()}&quot;],&quot;shape&quot;:&quot;variable&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="15" value="" style="strokeWidth=2;sharp=1;" parent="1" source="9" target="13" edge="1">
      <mxGeometry relative="1" as="geometry"/>
      <JsonProperty as="data">
        {&quot;value&quot;:&quot;&quot;,&quot;exception-flow&quot;:&quot;0&quot;,&quot;lineWidth&quot;:&quot;2&quot;,&quot;line-style&quot;:&quot;sharp&quot;,&quot;lineColor&quot;:&quot;black&quot;,&quot;condition&quot;:&quot;&quot;,&quot;transmit-variable&quot;:&quot;1&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="16" value="输出" style="output" parent="1" vertex="1">
      <mxGeometry x="720" y="80" width="32" height="32" as="geometry"/>
      <JsonProperty as="data">
        {&quot;value&quot;:&quot;输出&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;datasourceId&quot;:&quot;452876640fb2d0925b18e85d7edadec3&quot;,&quot;tableName&quot;:&quot;&quot;,&quot;csvName&quot;:&quot;&quot;,&quot;csvEncoding&quot;:&quot;GBK&quot;,&quot;output-name&quot;:[&quot;名称&quot;,&quot;图片&quot;,&quot;价格&quot;],&quot;loopCount&quot;:&quot;&quot;,&quot;output-value&quot;:[&quot;${name}&quot;,&quot;${img}&quot;,&quot;${price}&quot;],&quot;output-all&quot;:&quot;0&quot;,&quot;output-database&quot;:&quot;0&quot;,&quot;output-csv&quot;:&quot;0&quot;,&quot;shape&quot;:&quot;output&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="17" value="" style="strokeWidth=2;sharp=1;" parent="1" source="13" target="16" edge="1">
      <mxGeometry relative="1" as="geometry"/>
      <JsonProperty as="data">
        {&quot;value&quot;:&quot;&quot;,&quot;exception-flow&quot;:&quot;0&quot;,&quot;lineWidth&quot;:&quot;2&quot;,&quot;line-style&quot;:&quot;sharp&quot;,&quot;lineColor&quot;:&quot;black&quot;,&quot;condition&quot;:&quot;&quot;,&quot;transmit-variable&quot;:&quot;1&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="18" value="开始抓取" style="request" parent="1" vertex="1">
      <mxGeometry x="870" y="80" width="32" height="32" as="geometry"/>
      <JsonProperty as="data">
        {&quot;value&quot;:&quot;开始抓取&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;method&quot;:&quot;POST&quot;,&quot;sleep&quot;:&quot;&quot;,&quot;timeout&quot;:&quot;&quot;,&quot;response-charset&quot;:&quot;&quot;,&quot;retryCount&quot;:&quot;&quot;,&quot;retryInterval&quot;:&quot;&quot;,&quot;parameter-name&quot;:[&quot;name&quot;,&quot;img&quot;,&quot;price&quot;],&quot;parameter-description&quot;:[&quot;&quot;,&quot;&quot;,&quot;&quot;],&quot;body-type&quot;:&quot;none&quot;,&quot;body-content-type&quot;:&quot;application/json&quot;,&quot;loopCount&quot;:&quot;&quot;,&quot;url&quot;:&quot;http://localhost:9528/goods/addGoods&quot;,&quot;proxy&quot;:&quot;&quot;,&quot;parameter-value&quot;:[&quot;${name}&quot;,&quot;${img}&quot;,&quot;${price}&quot;],&quot;request-body&quot;:&quot;&quot;,&quot;follow-redirect&quot;:&quot;1&quot;,&quot;tls-validate&quot;:&quot;1&quot;,&quot;cookie-auto-set&quot;:&quot;1&quot;,&quot;repeat-enable&quot;:&quot;0&quot;,&quot;shape&quot;:&quot;request&quot;}
      </JsonProperty>
    </mxCell>
    <mxCell id="19" value="" style="strokeWidth=2;sharp=1;" parent="1" source="16" target="18" edge="1">
      <mxGeometry relative="1" as="geometry"/>
      <JsonProperty as="data">
        {&quot;value&quot;:&quot;&quot;,&quot;exception-flow&quot;:&quot;0&quot;,&quot;lineWidth&quot;:&quot;2&quot;,&quot;line-style&quot;:&quot;sharp&quot;,&quot;lineColor&quot;:&quot;black&quot;,&quot;condition&quot;:&quot;&quot;,&quot;transmit-variable&quot;:&quot;1&quot;}
      </JsonProperty>
    </mxCell>
  </root>
</mxGraphModel>

访问前端页面验证搜索

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>商品搜索</title>
    <script src="../js/jquery.min.js"></script>
    <script src="../js/vue.js"></script>
    <style>
        body {
            font: 12px/150% tahoma,arial,Microsoft YaHei,Hiragino Sans GB,"\u5b8b\u4f53",sans-serif;
            -webkit-font-smoothing: antialiased;
            color: #666;
            background: #fff;
        }
        ol, ul {
            list-style: none;
        }

        li {
            display: list-item;
            text-align: -webkit-match-parent;
        }
        .goods-list-v2 {
            margin-bottom: 20px;
            margin: 0 auto;
            width: 990px;
            height: auto;
            position: relative;
            z-index: 1;
            _zoom: 1;
        }

        .goods-list-v2 .gl-warp {
            width: 1200px;
            height: auto;
            margin-right: -40px;
        }

        .goods-list-v2 .gl-item {
            width: 240px;
            height: 350px;
            float: left;
            position: relative;
            z-index: 1;
            _display: inline;
            margin-right: 40px;
            margin-top: 10px;
        }

        .goods-list-v2.gl-type-3 .gl-item .gl-i-wrap {
            padding: 12px 9px;
        }
        .goods-list-v2 .gl-item .gl-i-wrap {
            width: 220px;
            position: absolute;
            z-index: 1;
            left: 0;
            top: 0;
            background: #fff;
            border: 1px solid #fff;
            padding: 38px 9px 10px;
            transition: border-color .1s ease;
        }

        .goods-list-v2 .gl-item .p-img {
            height: 220px;
            padding: 0;
            margin-bottom: 5px;
            position: relative;
        }

        .goods-list-v2 .gl-item .p-price {
            position: relative;
            line-height: 22px;
            height: 22px;
            overflow: hidden;
            width: 100%;
            margin: 0 0 8px;
        }

        .goods-list-v2 .gl-item .p-price strong {
            float: left;
            margin-right: 10px;
            color: #e4393c;
            font-size: 20px;
        }

        .goods-list-v2.gl-type-3 .gl-item .p-name-type-2, .goods-list-v2.gl-type-3 .gl-item .p-name-type-3 {
            height: 40px;
        }
        .goods-list-v2 .gl-item .p-name {
            height: 40px;
            margin-bottom: 8px;
            overflow: hidden;
        }
        .goods-list-v2.gl-type-3 .gl-item .p-name {
            height: 40px;
            line-height: 20px;
        }

        .goods-list-v2.gl-type-3 .gl-item .p-name em, .goods-list-v2.gl-type-3 .gl-item .p-name i {
            display: inline;
            height: auto;
        }

        em, i, u {
            font-style: normal;
        }

        .goods-list-v2 .gl-item .p-shop {
            line-height: 18px;
            height: 18px;
            overflow: hidden;
            margin-top: -3px;
            margin-bottom: 9px;
        }

        .goods-list-v2 .gl-item .p-shop span {
            display: inline-block;
            position: relative;
            height: 18px;
        }

        .gl-item .hd-shopname {
            display: inline-block;
            max-width: 122px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        .goods-list-v2 .gl-item .p-shop span .im-01, .goods-list-v2 .gl-item .p-shop span .im-02, .goods-list-v2 .gl-item .p-shop span .im-offline {
            cursor: pointer;
            position: absolute;
            right: -21px;
            top: 1px;
            display: block;
        }

        .im-01, .im-02, .im-offline {
            display: inline-block;
            width: 16px;
            height: 16px;
            background: url(//misc.360buyimg.com/product/search/1.0.8/css/i/bg-im@1x.png) no-repeat;
        }
    </style>
</head>
<body>
<div id="app">
    <form id="formId">
        商品名称:<input type="text" id="keywords" name="keywords" v-model="keywords"><br/>
        <input type="button" id="btn" @click="submitForm()" value="搜索">
    </form>
    <div id="J_goodsList" class="goods-list-v2 gl-type-3 J-goods-list">
        <ul class="gl-warp clearfix" data-tpl="3">
            <li data-sku="10064967079079" data-spu="10023381930842" ware-type="1" class="gl-item" v-for="item in goodsList">
                <div class="gl-i-wrap">
                    <div class="p-img">
                        <a target="_blank" :title="item.desc" href="//item.jd.com/10064967079079.html">
                            <img width="220" height="220" data-img="1" data-lazy-img="done" source-data-lazy-img="" :src="item.img">
                        </a>

                        <div data-lease="" data-catid="27769" data-venid="10220233" data-presale="0" data-done="1"></div>
                    </div>

                    <div class="p-price">
                        <strong class="J_10064967079079" data-presale="0" data-done="1" stock-done="1">
                            <em></em><i data-price="10064967079079">{{item.price}}</i>
                        </strong>
                    </div>
                    <div class="p-name p-name-type-2">
                        <a target="_blank" :title="item.desc" href="//item.jd.com/10064967079079.html" >
                            <em v-html="item.name"></em>
                        </a>
                    </div>

                    <div class="p-shop" data-dongdong="" data-selfware="0" data-score="5" data-reputation="100" data-done="1">
                        <span class="J_im_icon"><a target="_blank" class="curr-shop hd-shopname" onclick="searchlog(1,'10083927',0,58)" href="//mall.jd.com/index-10083927.html?from=pc" title="八哥官方旗舰店">八哥官方旗舰店</a><b class="im-01" style="background:url(//img14.360buyimg.com/uba/jfs/t26764/156/1205787445/713/9f715eaa/5bc4255bN0776eea6.png) no-repeat;" title="联系客服" onclick="searchlog(1,10083927,0,61)"></b></span>
                    </div>
                    <div class="p-icons" id="J_pro_10064967079079" data-done="1">
                    </div>
                </div>
            </li>

        </ul>
        <span class="clr"></span>
    </div>
</div>
<script>
        let vue = new Vue({
            el: '#app',
            data: {
                goodsList: [],
                keywords: "汉服女"
            },
            methods: {
                submitForm: function () {
                    // $.post(url, 表单参数序列化, function(data){...})

                    // 点击按钮时,发起起步请求,提交表单(异步表单提交)
                    // $("#formId").ajaxSubmit();   // 将表单转成异步表单,并提交
                    // $("#formId").ajaxForm();     // 将表单转成异步表单,不提交
                    $.post({
                       url: "http://localhost:9528/goods/queryGoodsByName",
                       type: "GET",
                       data: {"keywords": $("#keywords").val()},
                       success: function (data) {
                           if (data) {
                               // 操作成功跳转
                               vue.goodsList = data;
                           }
                       }
                    });
                }
            },
            mounted: function () {
                let _this = this;
                $.ajax({
                    url: "http://localhost:9528/goods/queryGoodsByName?keywords=汉服女",
                    type: "GET",
                    success: function (data) {
                        console.log(_this);
                        _this.goodsList = data;
                    }
                });
            }
        });
    </script>
</body>
</html>

福利时间
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值