搜索引擎开发
配置Redis,ES,MQ
修改配置文件
# 配置 redis
redis:
host: localhost
port: 6379
#配置 es
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: localhost:9300
repositories:
enabled: true
# rabbitmq
rabbitmq:
username: guest
password: guest
host: localhost
port: 5672
添加pom.xml 这里一定要注意版本 ,这里踩雷好几次。不是Springboot版本高就是es 的高
<!-- es-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!--整合rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
集成ES
编写文档
把我们需要的字段编写成es 的文档,这个type 属性其实在高版本的es 中是没有了 type 的属性
@Data
// es 的 文档
@Document(indexName = "post",type = "post",createIndex = true)
public class PostDocment implements Serializable {
@Id
private Long id;
// ik分词器
@Field(type = FieldType.Text, searchAnalyzer="ik_smart", analyzer = "ik_max_word")
private String title;
@Field(type = FieldType.Long)
private Long authorId;
@Field(type = FieldType.Keyword)
private String authorName;
private String authorAvatar;
private Long categoryId;
@Field(type = FieldType.Keyword)
private String categoryName;
private Integer level;
private Boolean recomment;
private Integer commentCount;
private Integer viewCount;
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd")
private Date created;
}
这里我有个坑 下面试我的版本对应
这里我的日期如果不加上格式化的话就会启动的时候报错。注意要加上日期格式化
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd")
private Date created;
写我们的dao 层 继承 es 的 ElasticsearchRepository
@Component
public interface PostRepository<T> extends ElasticsearchRepository<PostDocment, Long> {
}
这里我也有一个雷,原来我的Springboot 是最新的2.5.3 这里我继承这个ElasticsearchRepository的时候发现他的里面没有了search的方法只有了一个。版本减低后search这个方法也过时了。不过还可以用
编写服务类 用es 进行查询
@Service
public class SearchServiceimpl implements SearchService {
@Autowired
PostRepository postRepository;
@Autowired
ModelMapper modelMapper;
@Override
public IPage Search(Page page, String keyword) {
Long current = page.getCurrent() - 1;
Long size = page.getSize();
// 分页信息 mybatis plus 的 page 转成 jpa 的 page
Pageable pageable = PageRequest.of(current.intValue(), size.intValue());
// 搜索 es 得到 pageData
MultiMatchQueryBuilder query = QueryBuilders.multiMatchQuery(keyword, "title", "authorName", "categoryName");
// 注入
org.springframework.data.domain.Page<PostDocment> page1 = postRepository.search(query, pageable);
// 转换成 mybayis puls 的
IPage page2 = new Page(page.getCurrent(), page.getSize(), page1.getTotalElements());
page2.setRecords(page1.getContent());
return page2;
}
这里我们原来用的是 jpa 的分页查询但是我们要转换成mybatisplus 的
Contoller 层调用
@RequestMapping("/search")
public String search(String q) {
// 获取到分页
IPage pageData = searchService.Search(getPage(), q);
request.setAttribute("q", q);
request.setAttribute("pageData", pageData);
return "search";
}在这里插入代码片
前端代码我就不发了就是一个简单的搜索框,跳转后的页面
<#include "./inc/layout.ftl"/>
<@layout "搜索" >
<div class="layui-container">
<div class="layui-row layui-col-space15">
<div class="layui-col-md8">
<div class="fly-panel">
<div class="fly-panel-title fly-filter">
<a>您正在搜索关键字 “ ${q} ” - 共有 <strong>${pageData.total}</strong> 条记录</a>
<a href="#signin" class="layui-hide-sm layui-show-xs-block fly-right" id="LAY_goSignin" style="color: #FF5722;">去签到</a>
</div>
<ul class="fly-list">
<#list pageData.records as post >
<@postlist post></@postlist>
</#list>
</ul>
<@paging pageData ></@paging>
</div>
</div>
</div>
</div>
</@layout>
同步到ES 数据
现在虽然可以从 es里面查询数据 但是我们的es 里面还没有数据 所以需要需要同步数据,这里我设置的只有admin 可以同步数据
前端设置只有admin 用户的时候才会出现同步数据的按钮
就是这个按钮
编写按钮的Controller
@ResponseBody
@PostMapping("/initEsData")
public Result initEsData () {
int size =10000;
Page page = new Page();
page.setSize(size);
// 设置 条数
int total = 0;
for (int i = 0; i < 1000; i++) {
page.setCurrent(i);
IPage<PostVo> page1 = postService.paging(page, null, null, null, null, null);
int num = searchService.initEsData (page1.getRecords());
total += num;
// 当一页查不出来10000 条的时候就说明是最后一页了
if (page1.getRecords().size() <size) {
break;
}
}
return Result.success("Es " +total,null);
}
}
这里调用了我们之前写的一个pageing 的方法就是两个表查用户的数据
同步到 ES 的服务类
@Override
public int initEsData(List<PostVo> records) {
if (records == null || records.isEmpty()) {
return 0;
}
ArrayList<PostDocment> document = new ArrayList<>();
for (PostVo vo : records
) {
// 转换成 postDocment
PostDocment docment = modelMapper.map(vo, PostDocment.class);
document.add(docment);
}
// 保存到 es
postRepository.saveAll(document);
return document.size();
}
}
结果
同步成功 这样我么再次搜索的时候就能从 es 里面搜索了。不过我的数据其实都不至于用ES 来做。hhhhhhh但是我想试试ES 怎么在项目中使用。小机灵鬼
集成Rabbitmq
我们不能一直是点击按钮的时候同步ES 可以利用队列来做
创建Config 消息队列
@Configuration
public class RabbitMqConfig {
// 交换机的名称
public static final String ES_EXCHAGE ="es_exchage";
// 队列的名称
public static final String ES_QUEUE ="es_queue";
// 创建队列
@Bean ("exquue")
public Queue exquue () {
return new Queue(ES_QUEUE);
}
// 创建交换机
@Bean ("exchange")
public DirectExchange exchange () {
return new DirectExchange(ES_EXCHAGE);
}
// 将交换机跟 队列绑定起来
@Bean
Binding binding (@Qualifier ("exquue") Queue queue, @Qualifier("exchange") DirectExchange directExchange) {
return BindingBuilder.bind(queue).to(directExchange).with("es_key");
}
}
在Controller 使用 就是生产者
背景:因为我们不可能一直自己admin 同步 ES 的消息 所以我们需要自动的去更新我们的ES的数据 。
第一步
当我们更新或者删除的时候我们需要把消息发送给队列 就是我们的生产者。
在我们的Controller 类 找到 编辑的 发送消息到队列 里
// 编辑帖子 这里其实应该是 把编辑和 添加写到一个页面里面可是 我不太会使用 freemarker 的模块 所以只能分开了。功能 实现了就可以
@GetMapping("/post/edit")
public String edit() {
String id = request.getParameter("id");
MPost post1 = postService.getById(id);
if (!StringUtils.isEmpty(id)) {
MPost post = postService.getById(id);
Assert.isTrue(post != null, "改帖子已被删除");
Assert.isTrue(post.getUserId().longValue() == getProfilrId().longValue(), "没权限操作此文章");
request.setAttribute("post", post);
}
request.setAttribute("categories", imCategoryService.list());
// 通知MQ 我们是更新还是编辑了文章 1, 交换机的名字 , banindkey , 要发的内容
rabbitTemplate.convertAndSend(rabbitMqConfig.ES_EXCHAGE,"es_key"
,new PostMq(post1.getId(),PostMq.CREATE_OR_UPDATE));
return "/post/edit";
}
我们发送的数据还需要序列化 就类似我们的实体类所以我重新又建立了实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PostMq implements Serializable {
// 状态
public final static String CREATE_OR_UPDATE ="create_update";
public final static String REMOVE ="remove";
private Long postId;
private String type;
}
同理我们应该在添加文章以及删除文章的时候就做这个工作。
编写监听器
@Component
@Slf4j
@RabbitListener(queues = "es_queue")
public class MqMessageHandler {
@Autowired
SearchService searchService;
@RabbitHandler
public void handel(PostMq message) {
log.info("收到一条 message -----------------> {}" ,message.toString() );
switch (message.getType()) {
case PostMq.CREATE_OR_UPDATE:
searchService.createorupdate(message);
break;
case PostMq.REMOVE:
searchService.remove(message);
break;
default:
log.info("没有找到对应的消息{}", message);
break;
}
}
}
这个时候我们就可以监听到我们的队列里面的数据此时我们的rabbitmq Web 界面还没有数据 这个队列里面还没有队列
写完监听队列的动能开始编写方法
// 这个是添加或者更新的
@Override
public void createorupdate(PostMq message) {
Long postId = message.getPostId();
PostVo postVo = postService.selectOnePost(new QueryWrapper<MPost>().eq("p.id", postId));
// 转换成 postdocment
PostDocment docment = modelMapper.map(postVo, PostDocment.class);
// 再次保存到 ES
postRepository.save(docment);
// 写日志
log.info("es 索引更新成功 ------------------>{}" , docment.toString());
}
// 这个是删除的
@Override
public void remove(PostMq message) {
Long postId = message.getPostId();
// 删除索引
postRepository.deleteById(postId);
// postRepository.delete(postId);
// 打印日志
log.info("es 索引删除成功 ---------------》{}" , message.toString());
}
}
结果测试
启动项目
修改前的ES 中这个的数据是如下图所示
此时 ES 的数据就同步成功了。。。。。。不需要说只有管理员才可以去同步消息 假如说现在用户修改了自己的文章 那么索引库中的数据也实时的更新成功了!!!
总结
**这里有几个不足的情况,就是我的Api 好几个不太熟练 其次就是一定要注意ES 的版本问题 不然报错的概率让你摸不着头脑。。。。。暂时想不到其他需要注意的事项了 elasticsearch 我也就是个Api 调用只会。hahhahahahahha 还需要更加一层的学习啊!!RabbitMq 学了一点就想这加到我的项目中了,所以没考虑啥要不要考虑这个队列突然死掉我的数据怎么办的乱七八糟的东西 等我在学了 在考虑吧。慢慢来!!!!😁 **