最近项目需要消息中间件以及全文检索,考虑到需要近实时更新问题,没有采用Lucene 来写底层,直接用框架Solr好了
1.消息队列的搭建
下载ActiveMQ消息队列 地址:http://activemq.apache.org/activemq-5153-release.html
启动 (我这里是win64)
项目配置
pom.xml 加入如下依赖
org.springframework.boot
spring-boot-starter-activemq
MQ 消息发送实现类
package com.iking.message.provider.service.impl;
import javax.jms.Queue;
import javax.jms.Topic;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import com.alibaba.dubbo.config.annotation.Service;
import com.iking.common.api.message.service.TProducerService;
//楼主这里用的 dubbo 常规项目将这里换成spring的service注解即可
// @Service(version="1.0.0",interfaceName="com.iking.common.api.message.service.TProducerService")
@Service
public class TProducerServiceImpl implements TProducerService{
//这个TProducerService 面向接口编程 所写的一个接口
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate; //使用jms来发送消息
/**
* 获取 Queue 队列
* @param key 需要监听的Queue
* */
public Queue getQueue(String key){ //这个key 我这里采用人员id
System.out.println(" 创建了一个Queue key: " + key);
return new ActiveMQQueue(key);
}
/**
* point-to-point 消息生成 点对点 (p2p)
* @param message 消息对象
* @param id 需要发送消息的用户
* */
public void sendPointToPoint(Object message,String id) {
Queue queue = this.getQueue(id);
System.out.println(" 生成一条消息 :queue= " + queue);
this.jmsMessagingTemplate.convertAndSend(queue,message);
}
/**
* 获取 Topic 主题
* */
public Topic getTopic(String key)
{
System.out.println(" 创建了一个 Topic key: " + key);
return new ActiveMQTopic(key);
}
/**
* pub/sub 发布/订阅
* */
public void sendPubSub(Topic topic ,Object message) {
System.out.println(" 生成一条消息 :queue= " + topic);
this.jmsMessagingTemplate.convertAndSend(topic,message);
}
}
配置文件添加如下信息
#ActiveMQ 代理地址 代理服务器未启动无效
#集群配置 主tcp服务器地址 备用tcp服务器地址
spring.activemq.broker-url=failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61616)?timeout=3000&randomize=false
#username 代理服务器 用户名
spring.activemq.user=admin
#password 代理服务器 密码
spring.activemq.password=admin
#切换当前模式 point-to-point and pub/sub 默认为 false为点对点,true为发布订阅
spring.jms.pub-sub-domain=false
#是否启用内存模式 不开启内存模式
spring.activemq.in-memory=false
#信任所有的包
spring.activemq.packages.trust-all=true
#是否替换默认的connectionFactory
#spring.activemq.pool.enabled=false
#最大连接数
#spring.activemq.pool.max-connections=50
#超时时间
#spring.activemq.pool.expiry-timeout=10000
#空闲时间
#spring.activemq.pool.idle-timeout=30000
#会话缓存大小
#spring.activemq.pool.session-cache-size=100
下来还需要页面配置 应为此处楼主项目 需要将消息实时推送到用户手中 所以在页面用JS创建消费者
应为每个用户都需要实时接受消息 ,并切一登录也需要立即接受消息 所以应该写在登录成功后看到的第一个页面我这里是home页面
页面需要引入一个stomp.js 百度搜索即可
就是这个js
1=前后固定写法 中间路径端口自行配置 本地的话127.0.0.1 或者localhost 即可
2=帐号密码
3=用户登录会存储当前用户的id 我这里则用这个用户id 连接mq 应为后台也是以用户id 为队列id来创建推送消息的
4=后台推送的数据如下图箭头所对应的参数
5=开始连接mq
mq 管理页面
name 我这里设置用户id为name 前台页面也以此为key来创建连接消费消息
Number Of Pending Messages 等待消费的消息 “这个是当前未出队列的数量。可以理解为总接收数-总出队列数”
Number Of Consumers 消费者 “这个是消费者端的消费者数量”
Messages Enqueued 进入队列的消息 “进入队列的总数量,包括出队列的。 这个数量只增不减”
Messages Dequeued 出了队列的消息 “可以理解为是消费这消费掉的数量”
至此mq搭建完成
2.solr全文检索
solr 版本 4.10.3
tomcat 8(tmcat7及以上) jkd1.8(jdk1.7及以上即可)
solr 自带容器 但自处并不适用默认容器 先将默认容器改为tmocat
将此war包解压 将解压后的文件夹复制到tomcat webapps目录下
将solr 此目录下 ext问价夹中的所有jar包复制 到tmocat solr应用 web-inf/lib 内
将 solr原目录下 的 dist 和 contrib 复制到 tomcat wenpapps/solr路径下
在tomcat solr 应用内 新建 索引库 solr_home
然后新建solr_1
将solr原路径下的 solr文件夹内的所有东西 复制到tomcat solr_1 文件加中(这是在搭建solr_home)
复制到
打开 tomcat solr 应用 web-inf 内的web.xml 进行配置
进行如下配置
至此solr 服务独立搭建完成 可以运行这个tmocat了
打开http://127.0.0.1:20188/solr/ 页面进行验证是否搭建成功
看到如下页面即为搭建成功
简单介绍这个管理页面
增加的时候要先手动创建响应文件夹
复制这个即可
接下来 让项目连接这个solr服务
下面上代码
先配置 配置 配置
pom.xml 加入如下信息
org.springframework.data
spring-data-solr
org.apache.solr
solr-solrj
4.10.3
指定版本 我这里用的 spring boot 但依旧指定版本 solr lucene 这东西版本变更太快
spring 核心概念 一切皆为 bean
所以为了符合这个理念
solr配置也采用bean的方式
package com.iking.outside.provider.config;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class SolrConfig {
@Autowired
private Environment env; //用于读取 spring boot 配置文件
@Bean
public SolrServer getSolrServer() {
System.out.println("solr 依赖注入启动...");
return new HttpSolrServer(env.getProperty("solr.service.url"));
//这个key 随便写的 但别用spring 开头 应为spring 也整合了solr 用spring 开头 可能会引起意想不到的错误
}
}
application.properties 文件加入如下信息
#solr
solr.service.url=http://localhost:20188/solr/
solr 服务实现类 依旧的面向接口编程
接口:
package com.iking.common.api.outsid.service;
import java.util.List;
import java.util.Map;
public interface ISolrService {
/**
* @warning 业务插入执行完毕 请调用 commit();
* 增加文档集合
* @param list 业务实体数据集合
* */
public void addList(List> list) ;
/**
* @warning 业务插入执行完毕 请调用 commit();
* 增加文档
* @param entity 业务实体对象
* */
public void add(Object entity) ;
/**
* @warning 业务删除执行完毕 请调用 commit();
* 删除文档
* @param entity 业务实体对象
* */
public void deleteDocument(Object entity) ;
/**
* @warning 业务更新执行完毕 请调用 commit();
* 修改文档
* @param entity 业务数据对象
* */
public void updateDocument(Object entity) ;
/**
* 提交
* */
public void commit();
/**
* 所有涉及 字段名称 请遵循如下规则 否则会造成数据无法查询
* name == 'id' 则传入即可,无需处理
*
*
* type == (byte(Byte) || short(Short) || Integer(int) 则 name += "_i"
*
*
* type == Long(long) 则 name += "_l"
*
*
* type == Float(float) 则 name += "_f"
*
*
* type == Double(double) 则 name += "_d"
*
*
* type == Boolean(boolean) 则 name += "_b"
*
*
* type == (Character(char)||String) 则 name += "_s"
*
*
* type == Date 则 name += "_dt"
*
*
* 查询
* @param param 多条件集合
* @param fieldName 关键词字段名称
* @param q 关键词
* @param isHighlight 是否高亮显示
*
* 添加过滤条件: k=fq v=过滤字段名称&过滤字段值
*
*
* 添加排序: k=sort v=排序条件字段名称&ASC/DESC
*
*
* 添加分页: k=page,v=开始数&显示行数
*
*
* 默认查询域: k=df v=默认查询与字段名称&字段值 --- 已禁用
*
*
* 指定查询域: k=fl v=查询域字段名称1&查询域字段名称2&查询域字段名称3&....查询域字段名称n
*
*
* 高亮:
*
*
* 高两域 默认未关键字 q 所对应的域 fieldName
*
* 字体颜色:k=h&color v=颜色具体值 例如 red blue
*
*
* 字体大小:k=h&size v=具体数值
*
*
* 设置高亮请开启 isHighlight=true 否则不生效 默认红色
*
* @return List> 具体类型集合
* */
public List> solrSearch(Map param,String fieldName,String q,boolean isHighlight,Class>[] entitys) ;
/**
* @warning 此方法禁用,业务删除执行完毕 请调用 commit();
* 删除索引库
* @param entity 业务实体对象
* @deprecated
* */
public void deleteDocumentAll( );
/**
* 回滚
* */
public void rollback();
}
*****************************************实现类*******************************
package com.iking.outside.provider.service;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.ORDER;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.dubbo.config.annotation.Service;
import com.iking.common.api.outsid.service.ISolrService;
/**
* solr 增删改查 实现类
* @author 418233
* @date 2018年4月3日 10:51:27
* @version 1.0
* */
//@Service(version="1.0.0",interfaceName="com.iking.common.api.outsid.service.ISolrService",timeout=50000) //换成spring 的Service 注解即可
@Service
public class SolrServiceImpl implements ISolrService {
@Autowired
private SolrServer solrService; //这里自动装配的 就是刚才配置的bean SolrConfig.getSolrServer()方法返回的bean
/**
* @warning 业务插入执行完毕 请调用 commit();
* 增加文档集合
* @param list 业务实体数据集合
* */
public void addList(List> list){
List docs = new ArrayList<>();
for (Object obj : list) {
docs.add(reflect2Doc(obj));
}
this.add(docs);
}
/**
* @warning 业务插入执行完毕 请调用 commit();
* 增加文档
* @param entity 业务实体对象
* */
public void add(Object entity) {
this.add(reflect2Doc(entity));
}
/**
* @warning 业务删除执行完毕 请调用 commit();
* 删除文档
* @param entity 业务实体对象
* */
public void deleteDocument(Object entity){
Class> clazz = entity.getClass();
for(Field f : clazz.getDeclaredFields()){
f.setAccessible(true);
String fileName = f.getName();
fileName += getFieldNameSuffix(f.getType());
Object val = null;
try {
val = f.get(entity);
} catch (IllegalArgumentException | IllegalAccessException e1) {
throw new RuntimeException(" SolrServiceImpl.deleteDocument(...) 失败! \r " + e1);
}
if(val == null) continue;
try {
this.solrService.deleteByQuery(fileName + ":" + val,1000);
} catch (Exception e) {
throw new RuntimeException(" SolrServiceImpl.deleteDocument(...) 失败! \r " + e);
}
}
}
/**
* @warning 此方法禁用,业务删除执行完毕 请调用 commit();
* 删除索引库
* @param entity 业务实体对象
* @deprecated
* */
public void deleteDocumentAll( ){
try {
this.solrService.deleteByQuery("*:*");
} catch (SolrServerException | IOException e) {
throw new RuntimeException(" SolrServiceImpl.deleteDocumentAll() 失败! \r " + e);
}
System.out.println(" 索引库删除成功! 请重新创建! ");
}
/**
* @warning 业务更新执行完毕 请调用 commit();
* 修改文档
* @param entity 业务数据对象
* */
public void updateDocument(Object entity) {
this.add(entity);
}
/**
* 所有涉及 字段名称 请遵循如下规则 否则会造成数据无法查询
* name == 'id' 则传入即可,无需处理
*
*
* type == (byte(Byte) || short(Short) || Integer(int) 则 name += "_i"
*
*
* type == Long(long) 则 name += "_l"
*
*
* type == Float(float) 则 name += "_f"
*
*
* type == Double(double) 则 name += "_d"
*
*
* type == Boolean(boolean) 则 name += "_b"
*
*
* type == (Character(char)||String) 则 name += "_s"
*
*
* type == Date 则 name += "_dt"
*
*
* 查询
* @param param 多条件集合
* @param q 关键词
* @param fieldName 关键词字段名称
* @param isHighlight 是否高亮显示
* @param entity 实体类型
*
* 添加过滤条件: k=fq v=过滤字段名称&过滤字段值
*
*
* 添加排序: k=sort v=排序条件字段名称&ASC/DESC
*
*
* 添加分页: k=page,v=开始数&显示行数
*
*
* 默认查询域: k=df v=默认查询与字段名称&字段值 --- 已禁用
*
*
* 指定查询域: k=fl v=查询域字段名称1&查询域字段名称2&查询域字段名称3&....查询域字段名称n
*
*
* 高亮:
*
*
* 高两域 默认未关键字 q 所对应的域 fieldName
*
* 字体颜色:k=h&color v=颜色具体值 例如 red blue
*
*
* 字体大小:k=h&size v=具体数值
*
*
* 设置高亮请开启 isHighlight=true 否则不生效 默认红色
*
* @return List> 具体类型集合
* */
public List> solrSearch(Map param,String fieldName,String q,boolean isHighlight,Class>[] entitys) {
return this.search(param, fieldName, q, isHighlight,entitys);
}
/**
* 提交
* */
public void commit() {
try {
solrService.commit(true,true);
} catch (SolrServerException | IOException e) {
throw new RuntimeException(" SolrServiceImpl.commit() 提交失败! \r " + e);
}
}
/**
* 回滚
* */
public void rollback(){
try {
solrService.rollback();
} catch (SolrServerException | IOException e) {
throw new RuntimeException(" SolrServiceImpl.rollback() 回滚失败! \r " + e);
}
}
/**
* @param bean 业务数据对象
* @param fieldName 字段名称
* @param clazz 实体类型
* */
private SolrInputDocument reflect2Doc(Object bean){
SolrInputDocument doc = null;
Class> entity = bean.getClass();
Field[] fields = entity.getDeclaredFields();
doc = new SolrInputDocument();
for (Field f : fields) {
f.setAccessible(true);
if(!isFinal(f.getModifiers())){
try {
if("fId".equals(f.getName())){
this.setField(doc,"id", f.get(bean), f.getType());
}else{
this.setField(doc, f.getName(), f.get(bean), f.getType());
}
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(" SolrServiceImpl.reflect2Doc 反射获取bean方法返回值失败! " + e);
}
}
}
doc.setField("classes_s", entity.toString()); //增加类型信息
return doc;
}
/**
* 设置域名
* */
private void setField(SolrInputDocument doc,String name,Object val,Class> type){
if(!"id".equals(name)){
name += getFieldNameSuffix(type);
}
doc.setField(name, val);
}
/**
* 获取字段后缀
* */
private String getFieldNameSuffix(Class> type){
String suffix = "";
if (type == Byte.class || type == byte.class) {
suffix = "_i";
} else if (type == Short.class || type == short.class) {
suffix = "_i";
} else if (type == Integer.class || type == int.class) {
suffix = "_i";
} else if (type == Long.class || type == long.class) {
suffix = "_l";
} else if (type == Float.class || type == float.class) {
suffix = "_f";
} else if (type == Double.class || type == double.class) {
suffix = "_d";
} else if (type == Boolean.class || type == boolean.class) {
suffix = "_b";
} else if (type == Character.class || type == char.class) {
suffix = "_s";
} else if (type == String.class) {
suffix = "_s";
} else if (type == Date.class) {
suffix = "_dt";
}
return suffix;
}
/**
* 是否被final修饰
* */
private boolean isFinal(int modifiers){
return (modifiers & Modifier.FINAL) != 0;
}
/**
* @param docs 文档集合
* */
private void add(List docs){
try {
solrService.add(docs);
} catch (SolrServerException | IOException e) {
throw new RuntimeException(" SolrServiceImpl.add(List docs) 增加失败! 请检查 docs 参数 " + e);
}
}
/**
* @param docs 文档
* */
private void add(SolrInputDocument doc){
try {
solrService.add(doc);
} catch (SolrServerException | IOException e) {
throw new RuntimeException(" SolrServiceImpl.add(SolrInputDocument doc) 增加失败! 请检查 doc 参数 " + e);
}
}
/**
* 查询
* @param param 多条件集合
* @param q 关键词
* @param fieldName 关键词字段名称
* @param isHighlight 是否高亮显示
* @param entity 实体类型
*
* */
private List> search(Map param,String fieldName,String q,boolean isHighlight,Class>[] entitys){
if(param == null)param = new HashMap<>();
//这里对应刚才那个管理页面的选项
String[] fqVal = existence(param.get("fq"));//过滤条件
String[] sortVal = existence( param.get("sort"));//排序
String[] pageVal = existence(param.get("page"));//分页
String[] flVal = existence(param.get("fl"));//指定查询域
String[] hColor = existence(param.get("h&color"));//高亮字体颜色
String[] hSize = existence(param.get("h&size"));//高亮字体颜色
SolrQuery solrQuery = new SolrQuery();
//设置 条件
//关键词
if(StringUtils.isBlank(fieldName)&& StringUtils.isBlank(q)){
solrQuery.setQuery("*:*");//查所有
}else{
solrQuery.setQuery(fieldName + ":*" + q + "*");
}
//过滤条件
if(fqVal != null)
solrQuery.set("fq",fqVal[0] + ":"+ fqVal[1]);
//排序
if(sortVal != null){
ORDER order = "ASC".equals(sortVal[1]) ? ORDER.asc : "DESC".equals(sortVal[1]) ? ORDER.desc : ORDER.asc;
solrQuery.addSort(sortVal[0] , order);
}
//分页
if(pageVal != null){
solrQuery.setStart(Integer.parseInt(pageVal[0]));
solrQuery.setRows(Integer.parseInt(pageVal[1]));
}else{
solrQuery.setStart(0);
solrQuery.setRows(10);
}
//默认查询域
//solrQuery.set("df","*");
//指定查询域
if(flVal != null){
StringBuilder sbl = new StringBuilder();
for(String str : flVal){
sbl.append(str + ",");
}
sbl.delete(sbl.length()-1,sbl.length());
solrQuery.set("fl",sbl.toString());
}
//高亮
if(isHighlight){
//开启高亮
solrQuery.setHighlight(true);
//设置高亮域
solrQuery.addHighlightField(fieldName);
//高亮前缀
//字体大小 ,字体颜色
String size = StringUtils.isNotBlank(hColor[0]) ? hColor[0] : "red";
String color = StringUtils.isNotBlank(hSize[0]) ? hSize[0] : "5";
solrQuery.setHighlightSimplePre("");
//高亮后缀
solrQuery.setHighlightSimplePost("");
}
//执行查询
QueryResponse response;
try {
response = solrService.query(solrQuery);
} catch (SolrServerException e) {
throw new RuntimeException(" SolrServiceImpl.search(...) 失败! 获取query 异常 \r " + e);
}
//文档结果集
SolrDocumentList docs = response.getResults();
//获取高亮结果集
Map>> map = null;
if(isHighlight)
map = response.getHighlighting();
//map k id v map
//map k 域名 v list
//list 结果
//总行数
long count = docs.getNumFound();
System.out.println("匹配文档数 : " + count);
int index = 0;
List
controller 层如何使用solrServiceImpl
先注入 此处注解换成 @Autowired 即可
先给个初始化方法
用于项目第一次启动构建索引库
查询
在这里面定义的
此处默认定义可以更改 不建议更改,若要更改需要详细学习 lucene 和solr
简单说一下 某些类型的字段是否 会被索引检查到 以及是否排序等等 都在此处定义 一般来说默认定义足够你用了,
另外说几点
在进行业务方法的增删改 都需要调用一下solr的方法进行同步更新索引库 增加修改 调用add()即可 id相同即为修改 否则为新增
删除调用deleteDocument()即可 只需传入实体类 id字段需要有值
调用solr除查询方法外 请调用commit() 进行提交 ,rollbcak()进行回滚 来对索引库进行维护
不调用commit()提交 增删改是不会生效的.
至此 solr也全部完事了。