Solr的原理及在项目中的使用实例

转:https://www.cnblogs.com/wang-meng/p/5819792.html

1,这里Solr主要是怎么使用的呢? 

当我们在前台页面搜索商品名称关键词时, 我们这时是在Solr库中去查找相应的商品信息, 然后将搜索关键词高亮.

2,那么Solr库中的商品信息又是如何添加的呢? 

当我们在给商品上架的时候, 将商品信息update 到mysql数据库中的bbs_product表中, 然后同样的将相应的信息 添加到Solr库中.

接下来就看代码的具体实现吧: 

一, 商品上架
Solr的原理及在项目中的使用实例

我们在这里点击上架按钮
list.jsp:

1 <div style="margin-top:15px;"><input class="del-button" type="button" value="删除" onclick="optDelete();"/><input class="add" type="button" value="上架" onclick="isShow(‘${name}‘, ‘${brandId }‘, ‘${isShow }‘ ,‘${pagination.pageNo }‘)"/><input class="del-button" type="button" value="下架" onclick="isHide();"/></div>

点击上架触发isShow事件:

<script type="text/javascript">
//上架
function isShow(name,brandId,isShow,pageNo){
    //请至少选择一个
    var size = $("input[name=‘ids‘]:checked").size();
    if(size == 0){
        alert("请至少选择一个");
        return;
    }
    //你确定上架吗
    if(!confirm("你确定上架吗")){
        return;
    }
    //提交 Form表单
    $("#jvForm").attr("action","/product/isShow.do?name="+ name +"&brandId="+brandId+"&isShow="+isShow+"&pageNo="+pageNo);
    $("#jvForm").attr("method","post");
    $("#jvForm").submit();
    
}
</script>

接着到Controller层:
ProductController.java:

//添加页面
    @RequestMapping("/isShow.do")
    public String isShow(Long[] ids, Model model){
        productService.isShow(ids);
        return "forward:/product/list.do";
    }

接着看Service层:
ProdcutServiceImpl.java:

1 //上架
     @Autowired
	private SolrServer solrServer;
    public void isShow(Long[] ids){
        Product product = new Product();
        product.setIsShow(true);
        for (Long id : ids) {
            //上下架状态
            product.setId(id);
            productDao.updateByPrimaryKeySelective(product);
            
            //TODO 保存商品信息到Solr服务器
            SolrInputDocument doc = new SolrInputDocument();
            //ID
            doc.setField("id", id);
            //名称
            Product p = productDao.selectByPrimaryKey(id);
            doc.setField("name_ik", p.getName());
            //图片URL
            doc.setField("url", p.getImgUrls()[0]);
            //品牌 ID
            doc.setField("brandId", p.getBrandId());
            //价格 sql查询语句: select price from bbs_sku where product_id = ? order by price asc limit 1
            SkuQuery skuQuery = new SkuQuery();
            skuQuery.createCriteria().andProductIdEqualTo(id);
            skuQuery.setOrderByClause("price asc");
            skuQuery.setPageNo(1);
            skuQuery.setPageSize(1);
            List<Sku> skus = skuDao.selectByExample(skuQuery);
            doc.setField("price", skus.get(0).getPrice());
            //...时间等 剩下的省略
            
            try {
                solrServer.add(doc);
                solrServer.commit();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //TODO 静态化
        }
    }

这里使用SolrInputDocument 来保存商品信息, 其中doc.setField("name_ik", p.getName());的name_ik 是我们在solr 配置文件配置的IK 分词器的字段, doc.setField("url", p.getImgUrls()[0]); 这里我们也只是取第一张图片的url用来展示.
这里我们还用到了skuQuery, 因为一个商品中不同的颜色不同的尺码都可能有不同的价格, 我们在这里 是取到同一个productId下价格最小的来给显示~
然后再就是将我们已经设置好的SolrInputDocument 

通过SolrServer 来提交到Solr服务器.

SolrServer是已经在spring中注册好了的, 在这里直接注入即可使用.
spring来管理Solr:
Solr的原理及在项目中的使用实例


到了这里上架的功能就做好了, 这也是给后面Solr查询做好铺垫.

二, 前台使用Solr查询
到了这里就开始查看前台页面了, 前台页面是扒的网上的, 具体业务逻辑是自己修改的, 页面如下:
Solr的原理及在项目中的使用实例
这里需要特殊说明一下, 我们配置的全局拦截器变成了: / , 而且过滤掉静态资源, 配置如下:
首先是babasport-portal project下的web.xml文件:

 View Code

第二个就是babasport-portal project下的spring配置文件中设置过滤掉静态资源:

<!-- 过滤静态资源 -->
    <mvc:resources location="/js/" mapping="/js/*.*"/>
    <mvc:resources location="/css/" mapping="/css/*.*"/>
    <mvc:resources location="/images/" mapping="/images/*.*"/>


这样就就可以直接访问了.

当我们输入2016 点击查询后会出现什么? 我把已经做好的页面展示一下: 
Solr的原理及在项目中的使用实例

那么就进入到实际的开发当中: 
当我们在搜索框输入2016 且点击 搜索时: 
Solr的原理及在项目中的使用实例

然后到Controller层去找到search方法:

@Autowired
    private SearchService searchService;
    
    //去首页
    @RequestMapping(value="/")
    public String index(){
        return "index";
    }
    
    //搜索
    @RequestMapping(value="/search")
    public String search(Integer pageNo, String keyword, String price, Long brandId,  Model model){
        //品牌结果集  Redis中
        List<Brand> brands = searchService.selectBrandListFromRedis();
        model.addAttribute("brands", brands);
        
        //map 装已经选择的条件
        Map<String, String> map = new HashMap<String, String>();
        if(null != brandId){
            for (Brand brand : brands) {
                if(brandId.equals(brand.getId())){
                    map.put("品牌", brand.getName());
                    break;
                }
            }
        }
        //价格 0-99 1600以上
        if(null != price){
            String[] split = price.split("-");
            //如果切割后的长度等于2 就说明这是一个价格区间
            if(split.length == 2){
                map.put("价格", price);
            }else {
                map.put("价格", price + "以上");
            }
        }
        model.addAttribute("map", map);
        
        Pagination pagination = searchService.selectPaginationFromSolr(pageNo, keyword, price, brandId);
        model.addAttribute("pagination", pagination);
        model.addAttribute("keyword", keyword);
        model.addAttribute("price", price);
        model.addAttribute("brandId", brandId);
        
        return "search";
    }

提示:

这里使用到了SolrService, 相信看我以前博文的朋友都知道这个地方还需要配置dubbo, 就是服务提供方和适用方, 这里为了简便直接略过, 实际开发中是必须要配置的, 否则就调用不了SolrService中的方法了.
这个controller 中往search.jsp中put了很多东西, 具体这些东西什么用我们可以先不管, 我们先看下search.jsp页面.
而且这个controller中查询brand 是从redis中查询出来的, 我们会在下面讲到这个.

<c:if test="${fn:length(map) != 0 }">
            <div class="sl-b-selected J_brandSelected">
                <span class="crumbs-arrow">已选条件:</span>
                    <c:forEach items="${map }" var="m">
                        <a title="依琦莲(yiqilian)"  href="javascript:;" class="crumb-select-item">
                            <b>${m.key }:</b><em>${m.value }</em><i></i>
                        </a>
                    </c:forEach>
            </div>
            </c:if>

上面这个地方就是为何要在controller设置map值了, 这个是显示已选择的过滤条件.

<c:if test="${empty brandId }">
<div class="J_selectorLine s-brand">
    <div class="sl-wrap">
        <div class="sl-key"><strong>品牌:</strong></div>
        <div class="sl-value">
            <div class="sl-v-list">
                <ul class="J_valueList v-fixed">
                <c:forEach items="${brands }" var="brand">
                    <li id="brand-38118" data-initial="j" style="display:block;">
                        <a href="javascript:;" onclick="fqBrand(‘${brand.id }‘)" title="${brand.name }"><i></i>${brand.name }</a>
                    </li>
                </c:forEach>
                </ul>
            </div>
        </div>
    </div>
</div>
</c:if>
<c:if test="${empty price }">
<div id="J_selectorPrice" class="J_selectorLine s-line">
    <div class="sl-wrap">
        <div class="sl-key"><span>价格:</span></div>
        <div class="sl-value">
            <div class="sl-v-list">
                <ul class="J_valueList">
                    <li>
                        <a href="javascript:;" onclick="fqPrice(‘0-99‘)"><i></i>0-99</a>
                    </li>
                    <li>
                        <a href="javascript:;" onclick="fqPrice(‘100-299‘)"><i></i>100-299</a>
                    </li>
                    <li>
                        <a href="javascript:;" onclick="fqPrice(‘300-599‘)"><i></i>300-599</a>
                    </li>
                    <li>
                        <a href="javascript:;" onclick="fqPrice(‘600-999‘)"><i></i>600-999</a>
                    </li>
                    <li>
                        <a href="javascript:;" onclick="fqPrice(‘1000-1599‘)"><i></i>1000-1599</a>
                    </li>
                    <li>
                        <a href="javascript:;" onclick="fqPrice(‘1600‘)"><i></i>1600以上</a>
                    </li>
                </ul>
            </div>
        </div>
    </div>
</div>
</c:if>

接下来我们来看下对应的js方法:

<script type="text/javascript">
    var price = ‘${price}‘;
    var brandId = ‘${brandId}‘;
    //过滤品牌id
    function fqBrand(id){
        if(‘‘ != price){
            window.location.href="/search?keyword="+ ${keyword} + "&brandId="+ id+"&price="+price;
        }else{
            window.location.href="/search?keyword="+ ${keyword} + "&brandId="+ id;
        }
    }
    
    //过滤价格
    function fqPrice(id){
        if(‘‘ != brandId){
            window.location.href = "/search?keyword=${keyword}" + "&brandId=" + brandId + "&price=" + id;
        }else{
            window.location.href = "/search?keyword=${keyword}" + "&price=" + id;
        }
    }
</script>

这个就可以实现 添加 过滤条件的选项了.


三, 使用Redis 取出商品品牌列表
首先 当我们在后台添加或者修改品牌时, 我们应该同样将这个品牌添加到Redis中, 格式类似于: {"brandId":"brandName"}
controller层:(当我们在后台添加或者修改品牌)

@Autowired
    private Jedis jedis;
    //修改
    public void updateBrandById(Brand brand){
        //保存或修改 时修改Redis中的品牌, hmset适合批量添加品牌
        /*Map<String, String> map = new HashMap<String,String>();
        map.put(String.valueOf(brand.getId()), brand.getName());
        jedis.hmset("brand", map);*/
        jedis.hset("brand", String.valueOf(brand.getId()), brand.getName());
        brandDao.updateBrandById(brand);
    }

redis中有了品牌列表后, 然后就是查询了: 

@Autowired
    private Jedis jedis;
    //查询Redis中的品牌结果集
    public List<Brand> selectBrandListFromRedis(){
        List<Brand> brands = new ArrayList<Brand>();
        Map<String, String> hgetAll = jedis.hgetAll("brand");
        Set<Entry<String, String>> entrySet = hgetAll.entrySet();
        for (Entry<String, String> entry : entrySet) {
            Brand brand = new Brand();
            brand.setId(Long.parseLong(entry.getKey()));
            brand.setName(entry.getValue());
            brands.add(brand);
        }
        
        return brands;
    }


到了这里redis查询brand就完成了, 那么继续看下关于solr 是如何加入过滤条件的吧: 

@Autowired
    private SolrServer solrServer;
    //查询商品信息从Solr
    public Pagination selectPaginationFromSolr(Integer pageNo, String keyword, String price, Long brandId){
        ProductQuery productQuery = new ProductQuery();
        //当前页
        productQuery.setPageNo(Pagination.cpn(pageNo));
        //每页数
        productQuery.setPageSize(8);
        
        SolrQuery solrQuery = new SolrQuery();
        //关键词 商品名称
        solrQuery.set("q", "name_ik:"+keyword);
        //回显数据
        StringBuilder params = new StringBuilder();
        params.append("keyword=").append(keyword);
        
        //排序
        solrQuery.addSort("price", ORDER.asc);
        
        //高亮
        //1,设置, 打开高亮的开关
        solrQuery.setHighlight(true);
        //2, 设置高亮字段
        solrQuery.addHighlightField("name_ik");
        //3, 设置关键字高亮的样式 <span style=‘color:red‘>2016</span>
        //设置前缀和后缀
        solrQuery.setHighlightSimplePre("<span style=‘color:red‘>");
        solrQuery.setHighlightSimplePost("</span>");
        
        //过滤条件 品牌
        if(null != brandId){
            solrQuery.addFilterQuery("brandId:"+brandId);
            params.append("&brandId=").append(brandId);
        }
        //过滤价格 0-99  1600
        if(null != price){
            String[] split = price.split("-");
            //如果切割后的长度等于2 就说明这是一个价格区间
            if(split.length == 2){
                solrQuery.addFilterQuery("price:["+split[0]+" TO "+split[1]+"]");
            }else {
                solrQuery.addFilterQuery("price:["+split[0]+" TO *]");
            }
            params.append("&price=").append(price);
        }
        
        //分页  limit 开始行,每页数
        solrQuery.setStart(productQuery.getStartRow());
        solrQuery.setRows(productQuery.getPageSize());
        
        QueryResponse response = null;
        try {
            response = solrServer.query(solrQuery);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        //分析这个Map
        //第一层Map: Key String == ID : Value: Map
        //第二层Map: Key String == name_ik : Value: List
        //获取到List: String 0,1,2....
        Map<String, Map<String, List<String>>> highlighting = response.getHighlighting();
        
        
        List<Product> products = new ArrayList<Product>();
        //结果集
        SolrDocumentList docs = response.getResults();
        //总条数
        long numFound = docs.getNumFound();
        for (SolrDocument doc : docs) {
            Product product = new Product();
            //商品的ID
            String id = (String)doc.get("id");
            product.setId(Long.parseLong(id));
            
            //取第二层Map
            Map<String, List<String>> map = highlighting.get(id);
            //取List集合
            List<String> list = map.get("name_ik");
            
            //商品名称
            //String name = (String)doc.get("name_ik");
            //product.setName(name);
            product.setName(list.get(0)); //list.get(0) 中的name是已经设置为高亮的
            
            //图片
            String url = (String)doc.get("url");
            product.setImgUrl(url);
            //价格 这里的价格本身是保存在bbs_sku表中的, 而我们在这里将price属性直接添加到了Product中
            //因为我们在做上架的时候, 查询的是bbs_sku中price最小的值 然后保存到solr中的, 所以这里我们就直接将price属性添加到product中了
            //这里的价格只有一个值
            //Float price = (Float)doc.get("price");
            product.setPrice((Float)doc.get("price"));
            //品牌ID
            //Integer brandId = (Integer)doc.get("brandId");
            product.setBrandId(Long.parseLong(String.valueOf((Integer)doc.get("brandId"))));
            products.add(product);
        }
        
        Pagination pagination = new Pagination(
                    productQuery.getPageNo(),
                    productQuery.getPageSize(),
                    (int)numFound,
                    products
                );
        //页面展示
        String url = "/search";
        pagination.pageView(url, params.toString());
        
        return pagination;
    }