目录
商城业务-检索服务-SearchRequest构建-排序、分页、高亮&测试
商城业务-检索服务-SearchRequest构建-分析&封装
商城业务-检索服务-搭建页面环境
Nginx动静分离,将搜索页中的静态资源上传至/static/search文件夹下,将index.html搜索首页存放在gulimall-search服务的templates下
cd /mydata/nginx/html/static
mkdir search
使用thymeleaf模板引擎:
①导入thymeleaf的依赖
<!--导入thymeleaf依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
② 导入thymeleaf的命名空间
xmlns:th="http://www.thymeleaf.org"
③ 修改静态资源的请求路径,使用CTRL+R进行全部替换
所有动态请求search.gulimall.com的请求由Nginx转发给网关
①配置域名转发
②配置Nginx配置文件
cd /mydata/nginx/conf.d
vi gulimall.conf
重启nginx服务
docker restart nginx
③配置网关
- id: gulimall_host_route
uri: lb://gulimall-product # lb:负载均衡
predicates:
- Host=gulimall.com # **.xxx 子域名
- id: gulimall_search_route
uri: lb://gulimall-search # lb:负载均衡
predicates:
- Host=search.gulimall.com # **.xxx 子域名
商城业务-检索服务-调整页面跳转
配置热部署
①导入依赖
<!--导入热部署依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
② 开发期间默认关闭缓存
点击这几处要跳转到检索首页
鼠标右击,点击检查
修改请求路径
CTRL+F9重新编译
出现错误:访问到80端口
出现问题的原因:nginx配置出错不能正确路由跳转
解决方案:修改nginx配置文件
cd /mydata/nginx/conf/conf.d
vi gulimall.conf
重启nginx
docker restart nginx
关闭Product服务的缓存,重启服务
首页,点击搜索按钮要来到搜索页
点击手机1111111要来到搜索页
请求路径为http://search.gmall.com/list.html?catalog3Id=225,这是一个错误请求路径,缺少了gulimall而不是gumall
①将index.html修改为list.html
②编写控制类
③首页搜索栏修改为
④ 修改js并上传nginx,重启nginx
结果:
商城业务-检索服务-检索查询参数模型分析抽取
①通过首页搜索栏进行检索,传递keyword
②通过分类进行检索。传递catalog3Id
③复杂查询
排序:①综合排序②销量③价格 ,例如:通过销量降序排序或者升序排序,sort=saleCount_desc/saleCount_asc
过滤:①库存,例如:有库存->hasStock=1,无库存 -> hasStock=0 ②价格区间 ,例如: 价格位于 400 -900 -> skuPrice=400_900,价格低于900 -> skuPrice= _900,价格高于900 -> skuPrice=900_ ③品牌: 可以按照多个品牌进行筛选
聚合:属性:多个属性以:分割,1号属性网络可以是4G也可以是5G -> attrs=1_4G:5G
分页:页码
创建Vo,用于封装查询条件
商城业务-检索服务-检索返回结果模型分析抽取
以京东为例,搜索小米
默认:查询所有商品信息
1.小米所属的品牌 2.小米所属的分类 3.小米所属的属性
编写返回结果的Vo
商城业务-检索服务-检索DSL测试-查询部分
首先,这是一个复合查询即bool查询,将需要评分的检索条件写在must中,不需要评分的检索条件写在filter中。
①keyword的全文检索,例如:keyword=iphone
② 手机分类的检索,例如: catalogId=225 ,非文本字段检索用term
③ 品牌检索
④根据属性检索,属性未防止扁平化处理声明为nested,因此,需要使用nested查询
nested query文档地址:Nested query | Elasticsearch Guide [8.2] | Elastic
⑤是否有库存
⑥价格区间检索
⑦排序
⑧ 页码
⑨ 高亮,标题内容含有搜索内容则标题中含有的搜索内容标红
DSL语句:
GET /product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "iphone"
}
}
],
"filter": [
{
"term": {
"catalogId": {
"value": "225"
}
}
},
{
"terms": {
"brandId": [
"8",
"9"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "1"
}
}
},
{
"terms": {
"attrs.attrValue": [
"5G",
"4G"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "false"
}
}
},
{
"range": {
"skuPrice": {
"gte": 4999,
"lte": 5400
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 0,
"size": 10,
"highlight": {
"fields": {"skuTitle":{}},
"pre_tags": "<b style='color:red'>",
"post_tags": "</b>"
}
}
商城业务-检索服务-检索DSL测试-聚合部分
①product映射有些数据类型不允许索引,因此,创建新的映射,允许索引
PUT /gulimall_product
{
"mappings": {
"properties": {
"skuId":{
"type": "long"
},
"spuId":{
"type": "keyword"
},
"skuTitle":{
"type": "text",
"analyzer": "ik_smart"
},
"skuPrice":{
"type": "keyword"
},
"skuImg":{
"type": "keyword"
},
"saleCount":{
"type": "long"
},
"hasStock":{
"type": "boolean"
},
"hotScore":{
"type": "long"
},
"brandId":{
"type": "long"
},
"catelogId":{
"type": "long"
},
"brandName":{
"type": "keyword"
},
"brandImg":{
"type": "keyword"
},
"catelogName":{
"type": "keyword"
},
"attrs":{
"type": "nested",
"properties": {
"attrId":{
"type":"long"
},
"attrName":{
"type": "keyword"
},
"attrValue":{
"type":"keyword"
}
}
}
}
}
}
②数据迁移
③修改索引常量
④品牌聚合
⑤分类聚合
⑥属性聚合,应使用嵌入式聚合
nested aggregations文档地址:Nested Aggregations | Elasticsearch: The Definitive Guide [2.x] | Elastic
⑦完整DSL
GET /gulimall_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "iphone"
}
}
],
"filter": [
{
"term": {
"catalogId": {
"value": "225"
}
}
},
{
"terms": {
"brandId": [
"8",
"9"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "1"
}
}
},
{
"terms": {
"attrs.attrValue": [
"5G",
"4G"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "false"
}
}
},
{
"range": {
"skuPrice": {
"gte": 4999,
"lte": 5400
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 0,
"size": 10,
"highlight": {
"fields": {"skuTitle":{}},
"pre_tags": "<b style='color:red'>",
"post_tags": "</b>"
},
"aggs": {
"brand_agg": {
"terms": {
"field": "brandId",
"size": 10
},
"aggs": {
"brand_name_agg": {
"terms": {
"field": "brandName",
"size": 10
}
},
"brand_img-agg": {
"terms": {
"field": "brandImg",
"size": 10
}
}
}
},
"catalog_agg":{
"terms": {
"field": "catalogId",
"size": 10
},
"aggs": {
"catalog_name_agg": {
"terms": {
"field": "catelogName",
"size": 10
}
}
}
},
"attr_agg":{
"nested": {
"path": "attrs"
},
"aggs": {
"attr_id_agg": {
"terms": {
"field": "attrs.attrId",
"size": 10
},
"aggs": {
"attr_name_agg": {
"terms": {
"field": "attrs.attrName",
"size": 10
}
},
"attr_value_agg":{
"terms": {
"field": "attrs.attrValue",
"size": 10
}
}
}
}
}
}
}
}
⑧将gulimall_product映射和DSL进行保存
商城业务-检索服务-SearchRequest构建-检索
编写接口
导入ES客户端对象
整体逻辑:1.封装DSL 2.查询 3.封装响应结果
开始封装DSL
1.构建bool查询
2. 构建must查询
3. 构建filter中三级分类查询
4.构建filter中的品牌查询
5.构建filter中的库存查询
6.构建filter中的价格区间查询
7. 构建filter中的属性查询
商城业务-检索服务-SearchRequest构建-排序、分页、高亮&测试
1.构建排序
2. 构建分页
默认页码为1,页码大小为2方便测试
3.构建高亮
进行简单的测试,将打印的DSL复制到Kibana中看是否正确
商城业务-检索服务-SearchRequest构建-聚合
1.品牌聚合
2.分类聚合
3.属性聚合
商城业务-检索服务-SearchRequest构建-分析&封装
打上断点,根据返回的response封装响应结果
1对应于2
1. 封装所有记录
2. 封装品牌,聚合返回的类型可以从断点返回结果查看
3.封装分类
4.封装属性
5.封装页码
6.封装总记录数
7.封装总页数
商城业务-检索服务-验证结果封装正确性
高亮设置
商城业务-检索服务-页面数基本数据渲染
由于有库存的商品非常少,因此,不设置库存的默认值,前端传进来的参数不为空时再拼装上查询条件
将分页大小设置为16
动态获取页面显示数据
①商品显示
注意细节:th:text 会进行转义 ,th:utext不会进行转义
如果使用th:text,带keyword高亮之后,则会出现下面的结果:
②品牌显示
③分类显示
④ 属性显示
商城业务-检索服务-页面筛选条件渲染
1.按品牌条件筛选,"="
2.按分类条件筛选
3.按属性条件筛选
4. url拼接函数编写
商城业务-检索服务-页面分页数据渲染
1.搜索栏功能完成
为input创建id,方便后续拿到input中的输入;编写跳转方法
搜索框回显搜索内容,th:value 为属性设置值 ;param是指请求参数,param.keyword是指
请求参数中的keyword值
2.分页功能的完善
① 当前页码>第一页才能显示上一页,当前页码<总页码才能显示下一页
② 自定义属性用于保存当前页码,作用:用于替换请求参数中的pageNum值
③遍历显示页码
④ 当前页码显示特定的样式
⑤ 请求参数的替换
将a标签中href全部删除,添加a标签的class,为其绑定事件,并编写回调函数
$(this)指当前被点击的元素,return false作用:禁用默认行为,a标签可能会跳转
替换方法
function replaceParamVal(url,paramName,replaceVal){
var oUrl = url.toString();
var re = eval('/('+paramName+'=)([^&]*)/gi');
var nUrl = oUrl.replace(re,paramName+'='+replaceVal);
return nUrl;
}
商城业务-检索服务-页面排序功能
为a标签定义class
为a标签绑定点击事件
为选中的元素设置样式
为选中的元素设置样式之前需要将所有元素的样式恢复成最初样式
使用toggleClass()为class加上desc,默认为降序排序
添加升降符号
$(this).text()获取当前点击元素的文本内容
添加升降符号之前需要清空元素的升降符号
将被选中元素的样式改变抽取成一个方法
function changeStyle(ele){
$(".sort_a").css({"color":"#333","border-color":"#CCC","background":"#FFF"})
$(ele).css({"color":"#FFF","border-color":"#e4393c","background":"#e4393c"})
$(ele).toggleClass("desc");
$(".sort_a").each(function (){
var text = $(this).text().replace("↓","").replace("↑","");
$(this).text(text);
});
if ($(ele).hasClass("desc")){
var text = $(ele).text().replace("↓","").replace("↑","");
text = text+"↓";
$(ele).text(text);
}else {
var text = $(ele).text().replace("↓","").replace("↑","");
text = text+"↑";
$(ele).text(text);
}
}
自定义属性赋值为某种排序
改写替换方法
function replaceOrAddParamVal(url,paramName,replaceVal){
var oUrl = url.toString();
if (oUrl.indexOf(paramName)!=-1){
var re = eval('/('+paramName+'=)([^&]*)/gi');
var nUrl = oUrl.replace(re,paramName+'='+replaceVal);
return nUrl;
}else {
if (oUrl.indexOf("?")!=-1){
var nUrl = oUrl+"&"+paramName+"="+replaceVal;
return nUrl;
}else {
var nUrl = oUrl+"?"+paramName+"="+replaceVal;
return nUrl;
}
}
}
跳转指定路径
出现问题: 通过toggleClass()为class添加desc,刷新或者跳转之后会丢失
商城业务-检索服务-页面排序字段回显
页面跳转之后样式回显,th:with 用于声明变量,#strings即调用字符串工具类
根据URL动态添加class
动态的添加升降符号
商城业务-检索服务-页面价格区间搜索
编写价格区间搜索栏
为button按钮绑定单击事件
价格回显
①获取skuPirce的值
②价格区间回显
#strings.substringAfter(name,prefix):获取prifix之后的字符串
#strings.substringBefore(name,suffix):获取suffix之前的字符串
拼接是否有货查询条件
为单选框绑定改变事件
通过调用prop('check')获取是否被选中,选中为true否则false
回显选中状态
商城业务-检索服务-面包屑导航
①编写面包屑导航栏Vo
② 封装面包屑导航栏数据
属性名的获取要通过远程服务调用product服务进行查询
①导入cloud的版本
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
② 导入cloud依赖管理
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
③ 导入openfeign的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
④ 开启远程服务调用功能
⑤编写接口,配置调用的服务名
⑥编写调用服务的接口,注意:全路径
⑦编写自己传key和返回值类型获取自己想要的数据类型方法,之前的只能获取data的数据
⑧编写返回类型的Vo,Vo和AttrRespVo属性一致
⑨封装属性名
商城业务-检索服务-条件删除与URL编码问题
①封装原生的查询条件
HttpServletRequest的getQueryString()方法可以获取url的请求参数
②封装链接
出现问题:路径替换失败
出现问题的原因:浏览器会将中文进行一个编码,而查询出来的属性值是中文
解决方案:将中文进行编码
注意:有些符号,浏览器的编码与java编码不一致
例如:'(':浏览器不进行编码,java会编码成%28;')':浏览器不进行编码,java会编码成%29;空格浏览器会编码成%20,java会编码成'+'
// 8.封装面包屑导航栏的数据
if (param.getAttrs()!=null && param.getAttrs().size()>0){
List<SearchResVo.NavVo> navVoList = param.getAttrs().stream().map(item -> {
SearchResVo.NavVo navVo = new SearchResVo.NavVo();
String[] s = item.split("_");
// 封装属性值
navVo.setAttrValue(s[1]);
//封装属性名
R r = productFeignService.info(Long.parseLong(s[0]));
if (r.getCode() == 0){
AttrResponseVo responseVo = r.getData("attr", new TypeReference<AttrResponseVo>() {});
navVo.setAttrName(responseVo.getAttrName());
}else {
// 出现异常则封装id
navVo.setAttrName(s[0]);
}
//封装链接即去掉当前属性的查询的url封装
String encode=null;
try {
encode = URLEncoder.encode(item,"UTF-8");
encode=encode.replace("%28","(").replace("%29",")").replace("+","%20");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String replace = param.get_queryString().replace("&attrs=" + encode, "");
navVo.setLink("http://search.gulimall.com/list.html?"+replace);
return navVo;
}).collect(Collectors.toList());
searchResVo.setNavs(navVoList);
}
导航栏回显编写
①右击检测,找到元素
改写 replaceOrAddParamVal默认是对属性进行一个替换,forceAdd是否强制添加的标识
商城业务-检索服务-添加筛选联动
完善品牌面包屑导航栏功能,分类面包屑导航栏也类似,不同之处是不用剔除,设置url
①为面包屑vo设置一个默认值
② 远程调用product服务查询品牌名称
远程服务调用,查询很费时,可以将查询的结果保存进缓存中 ,例如:
value:分区名,key:用于标识第几号属性
③将封装替换url的方法抽取出来
④编写面包屑导航栏功能
品牌面包屑导航栏,品牌筛选剔除
⑤创建一个list用于封装已经筛选的属性id