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">
{"spiderName":"京东搜索","submit-strategy":"random","threadCount":""}
</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">
{"shape":"start"}
</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">
{"value":"开始抓取","loopVariableName":"","method":"GET","sleep":"","timeout":"","response-charset":"","retryCount":"","retryInterval":"","body-type":"none","body-content-type":"text/plain","loopCount":"","url":"https://search.jd.com/search?keyword=汉服女","proxy":"","request-body":"","follow-redirect":"1","tls-validate":"1","cookie-auto-set":"1","repeat-enable":"0","shape":"request"}
</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">
{"value":"","exception-flow":"0","lineWidth":"2","line-style":"sharp","lineColor":"black","condition":"","transmit-variable":"1"}
</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">
{"value":"定义变量","loopVariableName":"","variable-name":["goodsList","goodLi"],"variable-description":["",""],"loopCount":"","variable-value":["${resp.selector('#J_goodsList')}","${goodsList.selectors('li.gl-item')}"],"shape":"variable"}
</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">
{"value":"","exception-flow":"0","lineWidth":"2","line-style":"sharp","lineColor":"black","condition":"","transmit-variable":"1"}
</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">
{"value":"循环","loopItem":"","loopVariableName":"","loopCount":"${goodLi}","loopStart":"0","loopEnd":"-1","shape":"loop"}
</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">
{"value":"","exception-flow":"0","lineWidth":"2","line-style":"sharp","lineColor":"black","condition":"","transmit-variable":"1"}
</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">
{"value":"定义变量","loopVariableName":"","variable-name":["name","img","price"],"variable-description":["","",""],"loopCount":"","variable-value":["${item.selector(\"div.p-name a em\").text()}","${item.selector(\"div.p-img img\").attr(\"data-lazy-img\")}","${item.selector(\"div.p-price i\").text()}"],"shape":"variable"}
</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">
{"value":"","exception-flow":"0","lineWidth":"2","line-style":"sharp","lineColor":"black","condition":"","transmit-variable":"1"}
</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">
{"value":"输出","loopVariableName":"","datasourceId":"452876640fb2d0925b18e85d7edadec3","tableName":"","csvName":"","csvEncoding":"GBK","output-name":["名称","图片","价格"],"loopCount":"","output-value":["${name}","${img}","${price}"],"output-all":"0","output-database":"0","output-csv":"0","shape":"output"}
</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">
{"value":"","exception-flow":"0","lineWidth":"2","line-style":"sharp","lineColor":"black","condition":"","transmit-variable":"1"}
</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">
{"value":"开始抓取","loopVariableName":"","method":"POST","sleep":"","timeout":"","response-charset":"","retryCount":"","retryInterval":"","parameter-name":["name","img","price"],"parameter-description":["","",""],"body-type":"none","body-content-type":"application/json","loopCount":"","url":"http://localhost:9528/goods/addGoods","proxy":"","parameter-value":["${name}","${img}","${price}"],"request-body":"","follow-redirect":"1","tls-validate":"1","cookie-auto-set":"1","repeat-enable":"0","shape":"request"}
</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">
{"value":"","exception-flow":"0","lineWidth":"2","line-style":"sharp","lineColor":"black","condition":"","transmit-variable":"1"}
</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>
福利时间