这篇文章主要是关于solr和普通用户之间的桥梁SearchService
,简单了解下整过工作流程.
为何要在Solr和用户之间加一层?
因为solr提供了url方式(REST风格)的API来进行增删改查,因此如果不加安全策略,别人在查询的同时可以修改你的数据,这是绝对不允许的。但是把solr的服务端口开放然后加安全策略的方式是不科学的,这个安全策略难以配置,并且漏洞很多,所以我们通过建立一个独立的web服务器来提供对外服务,solr服务器只对内网开放,这样就比较安全并容易控制。
在这里,我不直接提供传统意义上的web服务,而是采用WebService
的模式,参照REST风格风格提供API服务。优点是可以应对各种不同平台。
这样做的另一个好处是能轻松应对SolrCloud的扩展和并发量的剧增,如果后期并发增加,可以扩展SearchService
到多台web服务器,然后通过nginx做反向代理和负载均衡,将客户端的请求分散到不同的web服务器上。
用Servlet提供服务
- 代码请参考 SearchService-Github.
- 开发IDE为 IntelliJ IDEA 15,用gradle管理,使用Jetty插件。
- 项目依赖(项目根目录的build.gradle):
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
testCompile 'org.slf4j:slf4j-simple:1.7.20'
providedCompile 'javax.servlet:javax.servlet-api:3.0.1'
compile 'org.apache.solr:solr-solrj:5.5.0'
compile 'com.google.code.gson:gson:2.6.2'
compile 'com.squareup.okhttp:okhttp:2.7.5'
}
定义API参数
参数 | 意义(取值范围) | 默认值 |
---|---|---|
keyword | 关键词 | null |
point | 坐标点 | null |
distance | 距离 | 100KM |
bound_type | 界限方式{geofilt,bbox,linestring,polygon} | null |
boundary | 范围 | null |
sort_order | 排序方式 {score,distance} | null |
sight_type | 景点类型 | null |
place | 行政区划 | null |
page | 页数 | 1 |
rows | 每页记录条数 | 10 |
query_type | 查询方式{near,key,map} | near |
Solrj的使用
1.先来构建出来SolrClient
代码位置:github中trip-search项目中SearchService/src/main/java/com/fliaping/trip/search/MySolrClient.java
public class MySolrClient {
private static final String urlString = "http://localhost:8983/solr/trip";
private static HttpSolrClient solrClient ;
private static MySolrClient mySolrClient ;
private MySolrClient(){}
public static synchronized MySolrClient getInstance(){
if (mySolrClient == null) mySolrClient = new MySolrClient();
return mySolrClient;
}
public static HttpSolrClient getSolrClient(){
if (solrClient == null){
solrClient = new HttpSolrClient(urlString);
solrClient.setSoTimeout(1000); // socket read timeout
solrClient.setAllowCompression(true); // allow Compression
}
return solrClient;
}
}
2.设置查询参数,这里的内容较多,方法也各不相同,详细请参考 SolrJ Tutorial和官方 Using SolrJ
示例代码:
代码位置:github中trip-search项目中SearchService/src/main/java/com/fliaping/trip/search/DoQuery.java
solrQuery.setStart(start)
.setRows(rows)
.setQuery("{!geofilt}")
.set("indent","true")
.set("pt","22.5347168,113.971228") //当前坐标
.set("sfield",Setting.sfield) //位置域
.set("fl","_dist_:geodist(),*"); //返回结果中添加_dist_字段(到当前坐标的距离)
3.查询并得到结果
QueryResponse queryResponse = solrClient.query(solrQuery);
4.对结果进行自定义包装
代码位置:github中trip-search项目中SearchService/src/main/java/com/fliaping/trip/search/DoQuery.java
/**
* 对查询结果包装成为客户端可用的格式
* @param queryResponse solr的请求结果
* @param hasFacet 是否需要facet
* @return
*/
private String wrapResult(QueryResponse queryResponse,boolean hasFacet){
SightList sightList = new SightList();
if(hasFacet){
List<SightFacet> sightFacets = new ArrayList<SightFacet>();
//整理facet数据
List facet = queryResponse.getFacetFields();
for (int i = 0; i < facet.size(); i++) {
//取得每个facet的信息
FacetField ff = (FacetField) facet.get(i);
//System.out.println("name:"+ff.getName()+" valuecount:"+ff.getValueCount());
SightFacet sightFacet = new SightFacet(); //facet对象
sightFacet.setFacetName(ff.getName()); //设置facet的field名字
List<SightFacet.Item> items = new ArrayList<SightFacet.Item>();
//取得每个facet中的每个item
List<FacetField.Count> countList = ff.getValues();
for (int j = 0; j < countList.size(); j++) {
FacetField.Count count = countList.get(j);
if (count.getCount() <= 0) continue; //facet中没有数据的,不加入结果集
//System.out.println("name:"+count.getName()+" count:"+count.getCount());
SightFacet.Item item = new SightFacet.Item(count.getName(),count.getCount());
items.add(item);
}
sightFacet.setFacetCount(items.size()); //设置facet中的个数
sightFacet.setItems(items); //设置facet中的items
sightFacets.add(sightFacet); //添加到facet列表中
}
sightList.setSightFacets(sightFacets);
}
//整理景点数据
List<Sight> list = queryResponse.getBeans(Sight.class);
SolrDocumentList documentList = queryResponse.getResults();
sightList.setSights(list);
sightList.setTime(queryResponse.getQTime());
sightList.setTotalNum(documentList.getNumFound());
sightList.setNowPage((int) (documentList.getStart()/documentList.size()) + 1);
sightList.setTotalPage((int) (documentList.getNumFound()/documentList.size()) + 1);
//美观型json
Gson gson2 = new GsonBuilder().setPrettyPrinting().create();
String json = gson2.toJson(sightList);
//紧凑型json
//String json = sightList.toJson();
return json;
}
三种查询方式
- 附近查询、关键字查询、地图查询
根据参数判断执行的动作,通过query_type
参数确定查询方式调用不同函数。
附近搜索
代码位置:github中trip-search项目中SearchService/src/main/java/com/fliaping/trip/search/DoQuery.java
/**
* 附近搜索
*/
public void nearQuery(){
//http://localhost:9090/ss?query_type=near&distance=10&point=22.5347168,113.971228
//设置查询Query
solrQuery.setQuery("{!geofilt}");
//设置距离distance
int distance = notNull(request.getParameter(UrlP.distance.name()),3000); //距离默认值3000公里
if (distance >= 0 && distance < 40076) { //判断距离值有效
solrQuery.set("d",distance);
}
//设置坐标点
String point = notNull(request.getParameter(UrlP.point.name()),"22.5347168,113.971228"); //默认坐标世界之窗
solrQuery.set("pt",point);
//设置排序
solrQuery.setSort("geodist()", SolrQuery.ORDER.asc);
//设置显示字段 fl
solrQuery.setFields("_dist_:geodist()","*");
//设置facet
solrQuery.setFacet(true)
.setFacetMissing(true)
.set("facet.field","sight_type");
System.out.println(solrQuery.toQueryString());
try {
QueryResponse queryResponse = solrClient.query(solrQuery);
//对solr的查询结果,整合后返回
respJson(wrapResult(queryResponse,false));
System.out.println("distance:"+distance);
} catch (SolrServerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
关键词搜索
代码位置:github中trip-search项目中SearchService/src/main/java/com/fliaping/trip/search/DoQuery.java
public void keyQuery(){
//http://localhost:8983/solr/trip/select?start=0&rows=10&sfield=sight_coordinate&fl=_dist_:geodist(),*&q=石河子&pt=44.3093867,86.0555942&sort=geodist() asc&facet=true&facet.missing=true&facet.field=sight_type
//设置过滤
//solrQuery.set("fq",request.getParameter(UrlP.keyword.name()));
//设置关键字
String keyword = notNull(request.getParameter(UrlP.keyword.name()),"*:*");
solrQuery.setQuery(keyword);
//设置坐标点
String point = notNull(request.getParameter(UrlP.point.name()),"22.5347168,113.971228"); //默认坐标世界之窗
solrQuery.set("pt",point);
//设置排序
String sortOrder = notNull(request.getParameter(UrlP.sort_order.name()),SortOrder.distance.get()); //默认距离排序
if (SortOrder.distance.is(sortOrder)) { //距离排序
solrQuery.setSort("geodist()", SolrQuery.ORDER.asc);
}else if (SortOrder.score.is(sortOrder)){ //评分排序
solrQuery.setSort("sight_score_ctrip", SolrQuery.ORDER.desc);
}else if (SortOrder.price.is(sortOrder)){ //价格排序
// TODO: 5/15/16 价格排序
}else if (SortOrder.best.is(sortOrder)){ //综合排序
// TODO: 5/15/16 综合排序
}
//设置过滤
//设置景点类型过滤
String sight_type = request.getParameter(UrlP.sight_type.name());
if(sight_type != null){
String[] type = sight_type.split(",");
for (String item : type) {
if (item != null){
solrQuery.addFilterQuery("sight_type:"+item);
}
}
}
//设置高亮
/*solrQuery.setHighlight(true)
.setHighlightSimplePre("<em>")
.setHighlightSimplePost("</em>")
.set("hl.fl","sight_intro");*/
//设置facet
solrQuery.setFacet(true)
.setFacetMissing(true)
.set("facet.field","sight_type");
System.out.println(solrQuery.toQueryString());
try {
QueryResponse queryResponse = solrClient.query(solrQuery);
//对solr的查询结果,整合后返回
respJson(wrapResult(queryResponse,true));
} catch (SolrServerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}