热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

全文检索solr在商城案例中的使用

1、案例介绍以前自己练手的时候做的ssm、ssh的案例(一些传统项目比如crm,oa,物流项目等)基本上都是在一个工程里面写表现层(Controller)、服务层(Service)、持

1、案例介绍

以前自己练手的时候做的ssm、ssh的案例(一些传统项目比如crm,oa,物流项目等)基本上都是在一个工程里面写表现层(Controller)、服务层(Service)、持久层(Dao)再到数据库,但是在并发太高的情况下就显得力不从心,所以在商城中会采用分布式架构。

先说3个概念

  1. 分布式:按照功能点把系统拆分,拆分成独立的功能。单独为某一个节点添加服务器。需要系统之间配合才能完成整个业务逻辑。叫做分布式。
  2. 分布式架构:多个子系统相互协作才能完成业务流程。系统之间需要进行通信。
  3. SOA:Service Oriented Architecture面向服务的架构。也就是把工程拆分成服务层、表现层两个工程。服务层中包含业务逻辑,只需要对外提供服务即可。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。而电子商城项目就是基于soa架构的。
    商城架构图如下:
    这里写图片描述
    最后我这里实现了需要的工程如下。
    这里写图片描述

    在这里要介绍的是商品搜索功能。需要用到的工程是

e3-manager-web(war)(后台管理系统表现层)
e3-search(pom)(搜索服务层)
|--e3-search-interface(jar)
|--e3-search-service(war)
e3-search-web(war)(搜索系统表现层)

2、商品信息导入到solr索引库

需求
商品搜索是站内在索引库中搜索,所以索引库中必须有商品相关信息。那么就需要将数据库中需要的商品信息,以及商品分类等信息(根据业务需求)导入到索引库。后台管理页面如下:
这里写图片描述
当点击意见淡入商品数据到索引库按钮的时候,要求发送请求将商品信息导入到索引库。在介绍solr的时候说过多次,我们这里索引库中需要的商品信息如下:
1、商品Id
2、商品标题
3、商品卖点
4、商品价格
5、商品图片
6、分类名称

对应业务类型跟业务域如下:

"text_ik" class="solr.TextField">
class="org.wltea.analyzer.lucene.IKAnalyzer"/>


"item_title" type="text_ik" indexed="true" stored="true"/>
"item_sell_point" type="text_ik" indexed="true" stored="true"/>
"item_price" type="long" indexed="true" stored="true"/>
"item_image" type="string" indexed="false" stored="true" />
"item_category_name" type="string" indexed="true" stored="true" />

"item_keywords" type="text_ik" indexed="true" stored="false" multiValued="true"/>
"item_title" dest="item_keywords"/>
"item_sell_point" dest="item_keywords"/>
"item_category_name" dest="item_keywords"/>

分析:
这里写图片描述
当点击按钮时候,发送请求,当返回状态码为200提示导入成功
请求参数:无
请求地址:/index/item/import
返回类型:E3Result
具体实现:发送请求后,在服务层需要先将商品信息从dao层查到,但是查到的数据要求跟索引库里面的一一对应,也就是说不仅仅只有商品本身信息,还有关联的商品类别表的类别名称category_name,所以需要自己再封装个pojo(名字叫SearchItem)属性跟域一一对应。因为涉及到了两个表一个是商品表(tb_item)和商品类别表(tb_item_catagory),所以不要能用逆向工程生成的mapper(可认为是Dao,mybatis逆向工程生成的代码主要针对单表)。
所以在服务层需要自己写mapper,查询所有SearchItem.
服务层查询到了所有SearchItem之后,遍历,将这些数据写入到索引库中。

实现
在工程e3-search-service中
1、Dao
映射文件ItemMapper.xml中:



<mapper namespace="cn.e3mall.search.mapper.ItemMapper" >

<select id="getItemList" resultType="cn.e3mall.common.pojo.SearchItem">
SELECT
a.id,
a.title,
a.sell_point,
a.price,
a.image,
b. NAME category_name
FROM
tb_item a
LEFT OUTER JOIN tb_item_cat b ON a.cid = b.id
WHERE
a.`status` = 1;
select>
mapper>

封装的实体类pojo如下:写在e3-common中

public class SearchItem implements Serializable{

private String id;
private String title;
private String sell_point;
private Long price;
private String image;
private String category_name;
get、set方法

对应的ItemMapper如下:

public interface ItemMapper {
List getItemList();
}

注:用的框架是Spring+SpringMVC+Mybatis

2、Service
该层需要查询所有数据(用到mapper),并且导入数据到索引库(用到HttpSolrServer)所以需要在容器中配置一个HttpSolrServer。


<bean id="httpSolrServer" class="org.apache.solr.client.solrj.impl.HttpSolrServer">
<constructor-arg index="0" value="http://192.168.25.128:8080/solr/collection1"/>
bean>
/*
* 索引库维护Service
*/

@Service
public class SearchItemServiceImpl implements SearchItemService{

@Autowired
private ItemMapper itemMapper;
@Autowired
private SolrServer solrServer;

public E3Result importAllItems() {
try {

//查询商品列表
List itemList = itemMapper.getItemList();
//遍历商品列表
for (SearchItem searchItem : itemList) {
//创建文档对象
SolrInputDocument document = new SolrInputDocument();
//向文档对象中添加域
document.addField("id", searchItem.getId());
document.addField("item_title", searchItem.getTitle());
document.addField("item_sell_point", searchItem.getSell_point());
document.addField("item_price", searchItem.getPrice());
document.addField("item_image", searchItem.getImage());
document.addField("item_category_name", searchItem.getCategory_name());
//把文档对象写入索引库
solrServer.add(document);
}
//提交
solrServer.commit();
//返回导入成功
return E3Result.ok();
}catch (Exception e) {
e.printStackTrace();
return E3Result.build(500, "数据导入时发生异常");
}
}

}

注:E3Result是自定义返回类型,代码如下

public class E3Result implements Serializable{

// 响应业务状态
private Integer status;

// 响应消息
private String msg;

// 响应中的数据
private Object data;

public static E3Result build(Integer status, String msg, Object data) {
return new E3Result(status, msg, data);
}

public static E3Result ok(Object data) {
return new E3Result(data);
}

public static E3Result ok() {
return new E3Result(null);
}

public E3Result() {

}

public static E3Result build(Integer status, String msg) {
return new E3Result(status, msg, null);
}

public E3Result(Integer status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}

public E3Result(Object data) {
this.status = 200;
this.msg = "OK";
this.data = data;
}
get、set方法
}

服务层写完了就只用发布服务让表现层去调用服务即可,这里用的是Dubbo来发布服务,使用Zookeeper作为注册中心。跟Web Service或者CXF类似。



<dubbo:application name="e3-search" />
<dubbo:registry protocol="zookeeper"
address="192.168.25.128:2181" />


<dubbo:protocol name="dubbo" port="20882" />

<dubbo:service interface="cn.e3mall.search.service.SearchItemService" ref="searchItemServiceImpl" timeout="600000"/>

3、表现层
e3-manager-web
首先要调用服务

配置文件中:


<dubbo:application name="e3-search-web"/>
<dubbo:registry protocol="zookeeper" address="192.168.25.128:2181"/>
<dubbo:reference interface="cn.e3mall.search.service.SearchItemService" id="searchItemService"/>

Controller:

@Controller
public class SearchItemController {

@Autowired
private SearchItemService searchItemService;

@RequestMapping("/index/item/import")
@ResponseBody
public E3Result importItemList(){
E3Result result = searchItemService.importAllItems();
return result;
}
}

点击导入到索引库按钮后,提示导入成功,去solr后台管理界面去查询所有,看看记录是不是跟数据库中一样的,一样就表示成功了。

注:其实存在这么个问题,就是如果数据库中又添加了商品信息或者修改了商品信息,那么索引库跟数据库就不同步了,虽然可以再通过一键导入使得同步,但是商品信息频繁的变化那么就要频繁使用一键导入,数据量那么大,频繁导入的话效率会降低很多。而且如果某个商品下架,这个时候即使使用一键导入也不会同步了,因为导入的时候是id相同则覆盖存在没有的id的索引则添加,索引原先存在于索引库的商品信息并不会删除。存在这种情况的话,可以使用消息队列来解决,会在消息队列中(如ActiveMq)讲到。

3、商品搜索实现

需求
搜索框中输入关键字:如 苹果7 plus,显示搜索结果,要求标题中关键字高亮(红色)
这里写图片描述

这里写图片描述

服务层

在e3-search-service工程中
1、Dao
搜索dao层就写在e3-search-service模块
应该是封装了一个通用了方法:当传来查询对象SolrQuery时,返回查询结果。
根据页面e3-search-web中搜索页面需要参数:总记录数recordCount,
总页数totalPages,搜索的商品列表itemList。当前页码由页面传来,每页显示有开发人员固定好。所以查询开始位置就能知道,页数也可以有总记录数跟每页显示条数记录下来,
所以需要个pojo来封装这些信息.写在e3-common中。

/*
* 商品搜索Dao
*/

@Repository
public class SearchDao {

@Autowired
private SolrServer solrServer;
/*
* 根据查询条件查询索引
*/

public SearchResult search(SolrQuery query) throws Exception{
//根据query查询索引库
QueryResponse queryRespOnse= solrServer.query(query);
//取查询结果
SolrDocumentList solrDocumentList = queryResponse.getResults();
//取查询结果总记录数
long numFound = solrDocumentList.getNumFound();
SearchResult result = new SearchResult();
result.setRecordCount(numFound);
//取商品列表,需要取高亮显示
Map>> highlighting = queryResponse.getHighlighting();


List itemList = new ArrayList<>();
for (SolrDocument solrDocument : solrDocumentList) {
SearchItem item = new SearchItem();
item.setId((String)solrDocument.get("id"));
item.setCategory_name((String)solrDocument.get("item_category_name"));
item.setImage((String)solrDocument.get("item_image"));
item.setPrice((long)solrDocument.get("item_price"));
item.setSell_point((String)solrDocument.get("item_sell_point"));
//取高亮
List list = highlighting.get(solrDocument.get("id")).get("item_title");
String title = "";
if(list!=null&&list.size()>0){
title = list.get(0);
}else{
title = (String)solrDocument.get("item_title");
}
item.setTitle(title);
//添加到商品列表
itemList.add(item);
}
result.setItemList(itemList);
//返回结果
return result;
}

}

2、Service
从表现层传来搜索关键字,当前页,每页显示条数,创建搜索对象SolrQuery,设置好查询条件,调用上面写的dao的search方法获得查询结果返回。

/*
* 商品搜索service
*/

@Service
public class SearchServiceImpl implements SearchService{

@Autowired
private SearchDao searchDao;

public SearchResult search(String keyword, int page, int rows) throws Exception {
//创建一个SolrQuery对象
SolrQuery query = new SolrQuery();
//设置查询条件
query.setQuery(keyword);
//设置分页条件
if(page <= 0){
page = 1;
}
query.setStart((page-1)*rows);
query.setRows(rows);
//设置默认搜索域
query.set("df", "item_title");
//开启高亮显示,显示颜色为红色
query.setHighlight(true);
query.setHighlightSimplePre("");
query.setHighlightSimplePost("
");
//调用Dao执行查询
SearchResult searchResult = searchDao.search(query);

//计算总页数
long recordCount = searchResult.getRecordCount();
int totalPage = (int) (recordCount/rows);
if(recordCount%rows > 0) totalPage++;
//返回结果
searchResult.setTotalPages(totalPage);
return searchResult;
}

}

注:计算页数可以用:(总记录数+每页显示条数-1)/每页显示条数。
然后要发布服务。

interface="cn.e3mall.search.service.SearchService" ref="searchServiceImpl" timeout="600000"/>

表现层
在e3-search-web中
调用上面服务层发布的服务,接收页面请求参数,调用服务层方法。根据页面需要(当前页,每页显示条数,总记录数,总页数,搜索到的商品信息列表)对返回结果进行处理,绑定到request域,跳转的逻辑视图。

/*
* 商品搜索Controller
*/

@Controller
public class SearchController {

@Autowired
private SearchService searchService;
@Value("${SEARCH_RESULT_ROWS}")
private Integer SEARCH_RESULT_ROWS;
@RequestMapping("/search")
public String searchItemList(String keyword,
@RequestParam(defaultValue="1") Integer page,Model model) throws Exception{
keyword = new String(keyword.getBytes("iso-8859-1"), "utf-8");
//查询商品列表
SearchResult result = searchService.search(keyword, page, SEARCH_RESULT_ROWS);
//把结果传递给页面
model.addAttribute("query", keyword);
model.addAttribute("totalPages", result.getTotalPages());
model.addAttribute("page", page);
model.addAttribute("recordCount", result.getRecordCount());
model.addAttribute("itemList", result.getItemList());
return "search";
}
}

效果如下:
这里写图片描述

注:如果出现了下面的这种绑定异常
这里写图片描述
解决办法:


<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>

<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
resources>

推荐阅读
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • 在单位的一台4cpu的服务器上部署了esxserver,挂载了6个虚拟机,目前运行正常。在安装部署过程中,得到了cnvz.net论坛精华区 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 本文总结了淘淘商城项目的功能和架构,并介绍了传统架构中遇到的session共享问题及解决方法。淘淘商城是一个综合性的B2C平台,类似京东商城、天猫商城,会员可以在商城浏览商品、下订单,管理员、运营可以在平台后台管理系统中管理商品、订单、会员等。商城的架构包括后台管理系统、前台系统、会员系统、订单系统、搜索系统和单点登录系统。在传统架构中,可以采用tomcat集群解决并发量高的问题,但由于session共享的限制,集群数量有限。本文探讨了如何解决session共享的问题。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • iOS Swift中如何实现自动登录?
    本文介绍了在iOS Swift中如何实现自动登录的方法,包括使用故事板、SWRevealViewController等技术,以及解决用户注销后重新登录自动跳转到主页的问题。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • Sleuth+zipkin链路追踪SpringCloud微服务的解决方案
    在庞大的微服务群中,随着业务扩展,微服务个数增多,系统调用链路复杂化。Sleuth+zipkin是解决SpringCloud微服务定位和追踪的方案。通过TraceId将不同服务调用的日志串联起来,实现请求链路跟踪。通过Feign调用和Request传递TraceId,将整个调用链路的服务日志归组合并,提供定位和追踪的功能。 ... [详细]
  • 本文总结了初学者在使用dubbo设计架构过程中遇到的问题,并提供了相应的解决方法。问题包括传输字节流限制、分布式事务、序列化、多点部署、zk端口冲突、服务失败请求3次机制以及启动时检查。通过解决这些问题,初学者能够更好地理解和应用dubbo设计架构。 ... [详细]
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了markdown[软件代理设置]相关的知识,希望对你有一定的参考价值。 ... [详细]
  • DockerDataCenter系列(四)-离线安装UCP和DTR,Go语言社区,Golang程序员人脉社 ... [详细]
author-avatar
mobiledu2502917177
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有