springboot整合MeiliSearch轻量级搜索引擎

news/2025/3/15 15:22:51/

一、Meilisearch与Easy Search点击进入官网了解,本文主要从小微型公司业务出发,选择meilisearch来作为项目的全文搜索引擎,还可以当成来mongodb来使用。

二、starter封装

1、项目结构展示

2、引入依赖包

    <dependencies><dependency><groupId>cn.iocoder.boot</groupId><artifactId>yudao-common</artifactId></dependency><!-- meilisearch 轻量级搜索       --><!-- https://mvnrepository.com/artifact/com.meilisearch.sdk/meilisearch-java --><dependency><groupId>com.meilisearch.sdk</groupId><artifactId>meilisearch-java</artifactId><version>0.11.2</version></dependency><!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.40</version><scope>provided</scope></dependency><!-- Web 相关 --><dependency><groupId>cn.iocoder.boot</groupId><artifactId>yudao-spring-boot-starter-web</artifactId><scope>provided</scope> <!-- 设置为 provided,只有 OncePerRequestFilter 使用到 --></dependency></dependencies>

3、yml参数读取代码参考

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;/*** MeiliSearch 自动装配参数类* 2023年9月21日*/
@ConfigurationProperties("yudao.meilisearch")
@Data
@Validated
public class MeiliSearchProperties {/*** 主机地址*/private String hostUrl = "";/*** 接口访问标识*/private String apiKey = "123456";}

4、自动配置类代码参考

import com.meilisearch.sdk.Client;
import com.meilisearch.sdk.Config;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;import javax.annotation.Resource;/*** MeiliSearch 自动装配类* 2023年9月21日*/
@AutoConfiguration
@EnableConfigurationProperties({MeiliSearchProperties.class})
@EnableCaching
public class MeiliSearchAutoConfiguration {@ResourceMeiliSearchProperties properties;@Bean@ConditionalOnMissingBean(Client.class)Client client() {return new Client(config());}@Bean@ConditionalOnMissingBean(Config.class)Config config() {return new Config(properties.getHostUrl(), properties.getApiKey());}}

5、数据处理类参考

import com.meilisearch.sdk.json.GsonJsonHandler;import java.util.List;/*** MeiliSearch json解析类* 2023年9月21日*/
public class JsonHandler {private com.meilisearch.sdk.json.JsonHandler jsonHandler = new GsonJsonHandler();public <T> SearchResult<T> resultDecode(String o, Class<T> clazz) {Object result = null;try {result = jsonHandler.decode(o, SearchResult.class, clazz);} catch (Exception e) {e.printStackTrace();}return result == null ? null : (SearchResult<T>) result;}public <T> List<T> listDecode(Object o, Class<T> clazz) {Object list = null;try {list = jsonHandler.decode(o, List.class, clazz);} catch (Exception e) {e.printStackTrace();}return list == null ? null : (List<T>) list;}public String encode(Object o) {try {return jsonHandler.encode(o);} catch (Exception e) {e.printStackTrace();return null;}}public <T> T decode(Object o, Class<T> clazz) {T t = null;try {t = jsonHandler.decode(o, clazz);} catch (Exception e) {e.printStackTrace();}return t;}
}import java.util.List;
import java.util.Map;/*** MeiliSearch* 2023年9月21日*/
public class MatchedBean<T> {private T _formatted;private Map<String, List<Matching>> _matchesInfo;public T get_formatted() {return _formatted;}public void set_formatted(T _formatted) {this._formatted = _formatted;}public Map<String, List<Matching>> get_matchesInfo() {return _matchesInfo;}public void set_matchesInfo(Map<String, List<Matching>> _matchesInfo) {this._matchesInfo = _matchesInfo;}private class Matching {long start;long length;public long getStart() {return start;}public void setStart(long start) {this.start = start;}public long getLength() {return length;}public void setLength(long length) {this.length = length;}}
}import java.util.List;/*** MeiliSearch* 2023年9月21日*/
public class SearchResult<T> {private String query;private long offset;private long limit;private long processingTimeMs;private long nbHits;private boolean exhaustiveNbHits;private List<T> hits;public String getQuery() {return query;}public void setQuery(String query) {this.query = query;}public long getOffset() {return offset;}public void setOffset(long offset) {this.offset = offset;}public long getLimit() {return limit;}public void setLimit(long limit) {this.limit = limit;}public long getProcessingTimeMs() {return processingTimeMs;}public void setProcessingTimeMs(long processingTimeMs) {this.processingTimeMs = processingTimeMs;}public long getNbHits() {return nbHits;}public void setNbHits(long nbHits) {this.nbHits = nbHits;}public boolean isExhaustiveNbHits() {return exhaustiveNbHits;}public void setExhaustiveNbHits(boolean exhaustiveNbHits) {this.exhaustiveNbHits = exhaustiveNbHits;}public List<T> getHits() {return hits;}public void setHits(List<T> hits) {this.hits = hits;}@Overridepublic String toString() {return "SearchResult{" +"query='" + query + '\'' +", offset=" + offset +", limit=" + limit +", processingTimeMs=" + processingTimeMs +", nbHits=" + nbHits +", exhaustiveNbHits=" + exhaustiveNbHits +", hits=" + hits +'}';}
}

6、自定义注解代码参考

import java.lang.annotation.*;/*** MeiliSearch* 2023年9月21日*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MSFiled {/*** 是否开启过滤*/boolean openFilter() default false;/*** 是否不展示*/boolean noDisplayed() default false;/*** 是否开启排序*/boolean openSort() default false;/***  处理的字段名*/String key() ;
}import java.lang.annotation.*;/*** MeiliSearch* 2023年9月21日*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MSIndex {/*** 索引*/String uid() default "";/*** 主键*/String primaryKey() default "";/*** 分类最大数量*/int maxValuesPerFacet() default 100;/***  单次查询最大数量*/int maxTotalHits() default 1000;
}

7、基础操作接口封装

import cn.iocoder.yudao.framework.meilisearch.json.SearchResult;
import com.meilisearch.sdk.SearchRequest;
import com.meilisearch.sdk.model.Settings;
import com.meilisearch.sdk.model.Task;
import com.meilisearch.sdk.model.TaskInfo;import java.util.List;/*** MeiliSearch 基础接口* 2023年9月21日*/
interface DocumentOperations<T> {T get(String identifier);List<T> list();List<T> list(int limit);List<T> list(int offset, int limit);long add(T document);long update(T document);long add(List<T> documents);long update(List<T> documents);long delete(String identifier);long deleteBatch(String... documentsIdentifiers);long deleteAll();SearchResult<T> search(String q);SearchResult<T> search(String q, int offset, int limit);SearchResult<T> search(SearchRequest sr);Settings getSettings();TaskInfo updateSettings(Settings settings);TaskInfo resetSettings();Task getUpdate(int updateId);
}

8、基本操作实现

import cn.iocoder.yudao.framework.meilisearch.json.JsonHandler;
import cn.iocoder.yudao.framework.meilisearch.json.MSFiled;
import cn.iocoder.yudao.framework.meilisearch.json.MSIndex;
import cn.iocoder.yudao.framework.meilisearch.json.SearchResult;
import com.alibaba.fastjson2.JSON;
import com.meilisearch.sdk.*;
import com.meilisearch.sdk.model.*;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.*;/*** MeiliSearch 基本操作实现* 2023年9月21日*/
public class MeilisearchRepository<T> implements InitializingBean, DocumentOperations<T> {private Index index;private Class<T> tClass;private JsonHandler jsonHandler = new JsonHandler();@Resourceprivate Client client;@Overridepublic T get(String identifier) {T document;try {document = index.getDocument(identifier, tClass);} catch (Exception e) {throw new RuntimeException(e);}return document;}@Overridepublic List<T> list() {List<T> documents;try {documents = Optional.ofNullable(index.getDocuments(tClass)).map(indexDocument -> indexDocument.getResults()).map(result -> Arrays.asList(result)).orElse(new ArrayList<>());} catch (Exception e) {throw new RuntimeException(e);}return documents;}@Overridepublic List<T> list(int limit) {List<T> documents;try {DocumentsQuery query = new DocumentsQuery();query.setLimit(limit);documents = Optional.ofNullable(index.getDocuments(query, tClass)).map(indexDocument -> indexDocument.getResults()).map(result -> Arrays.asList(result)).orElse(new ArrayList<>());} catch (Exception e) {throw new RuntimeException(e);}return documents;}@Overridepublic List<T> list(int offset, int limit) {List<T> documents;try {DocumentsQuery query = new DocumentsQuery();query.setLimit(limit);query.setOffset(offset);documents = Optional.ofNullable(index.getDocuments(query, tClass)).map(indexDocument -> indexDocument.getResults()).map(result -> Arrays.asList(result)).orElse(new ArrayList<>());} catch (Exception e) {throw new RuntimeException(e);}return documents;}@Overridepublic long add(T document) {List<T> list = Collections.singletonList(document);return add(list);}@Overridepublic long update(T document) {List<T> list = Collections.singletonList(document);return update(list);}@Overridepublic long add(List documents) {int taskId;try {taskId = index.addDocuments(JSON.toJSONString(documents)).getTaskUid();} catch (Exception e) {throw new RuntimeException(e);}return taskId;}@Overridepublic long update(List documents) {int updates;try {updates = index.updateDocuments(JSON.toJSONString(documents)).getTaskUid();} catch (Exception e) {throw new RuntimeException(e);}return updates;}@Overridepublic long delete(String identifier) {int taskId;try {taskId = index.deleteDocument(identifier).getTaskUid();} catch (Exception e) {throw new RuntimeException(e);}return taskId;}@Overridepublic long deleteBatch(String... documentsIdentifiers) {int taskId;try {taskId = index.deleteDocuments(Arrays.asList(documentsIdentifiers)).getTaskUid();} catch (Exception e) {throw new RuntimeException(e);}return taskId;}@Overridepublic long deleteAll() {int taskId;try {taskId = index.deleteAllDocuments().getTaskUid();} catch (Exception e) {throw new RuntimeException(e);}return taskId;}@Overridepublic cn.iocoder.yudao.framework.meilisearch.json.SearchResult<T> search(String q) {String result;try {result = JSON.toJSONString(index.search(q));} catch (Exception e) {throw new RuntimeException(e);}return jsonHandler.resultDecode(result, tClass);}@Overridepublic cn.iocoder.yudao.framework.meilisearch.json.SearchResult<T> search(String q, int offset, int limit) {SearchRequest searchRequest = SearchRequest.builder().q(q).offset(offset).limit(limit).build();return search(searchRequest);}@Overridepublic SearchResult<T> search(SearchRequest sr) {String result;try {result = JSON.toJSONString(index.search(sr));} catch (Exception e) {throw new RuntimeException(e);}return jsonHandler.resultDecode(result, tClass);}@Overridepublic Settings getSettings() {try {return index.getSettings();} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic TaskInfo updateSettings(Settings settings) {try {return index.updateSettings(settings);} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic TaskInfo resetSettings() {try {return index.resetSettings();} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic Task getUpdate(int updateId) {try {return index.getTask(updateId);} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic void afterPropertiesSet() throws Exception {initIndex();}public Index getIndex() {return index;}/*** 初始化索引信息** @throws Exception*/private void initIndex() throws Exception {Class<? extends MeilisearchRepository> clazz = getClass();tClass = (Class<T>) ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[0];MSIndex annoIndex = tClass.getAnnotation(MSIndex.class);String uid = annoIndex.uid();String primaryKey = annoIndex.primaryKey();if (StringUtils.isEmpty(uid)) {uid = tClass.getSimpleName().toLowerCase();}if (StringUtils.isEmpty(primaryKey)) {primaryKey = "id";}int maxTotalHit=1000;int maxValuesPerFacet=100;if (Objects.nonNull(annoIndex.maxTotalHits())){maxTotalHit=annoIndex.maxTotalHits();}if (Objects.nonNull(annoIndex.maxValuesPerFacet())){maxValuesPerFacet=100;}List<String> filterKey = new ArrayList<>();List<String> sortKey = new ArrayList<>();List<String> noDisPlay = new ArrayList<>();//获取类所有属性for (Field field : tClass.getDeclaredFields()) {//判断是否存在这个注解if (field.isAnnotationPresent(MSFiled.class)) {MSFiled annotation = field.getAnnotation(MSFiled.class);if (annotation.openFilter()) {filterKey.add(annotation.key());}if (annotation.openSort()) {sortKey.add(annotation.key());}if (annotation.noDisplayed()) {noDisPlay.add(annotation.key());}}}Results<Index> indexes = client.getIndexes();Index[] results = indexes.getResults();Boolean isHaveIndex=false;for (Index result : results) {if (uid.equals(result.getUid())){isHaveIndex=true;break;}}if (isHaveIndex){client.updateIndex(uid,primaryKey);this.index = client.getIndex(uid);Settings settings = new Settings();settings.setDisplayedAttributes(noDisPlay.size()>0?noDisPlay.toArray(new String[noDisPlay.size()]):new String[]{"*"});settings.setFilterableAttributes(filterKey.toArray(new String[filterKey.size()]));settings.setSortableAttributes(sortKey.toArray(new String[sortKey.size()]));index.updateSettings(settings);}else {client.createIndex(uid, primaryKey);}}
}

9、指定自动配置类所在

10、项目有统一版本管理的设置下版本管理

二、项目引用

1、引入starter依赖(没有版本统一管理的要把version加上)

2、基本使用

2.1、建立索引(宽表)

import cn.iocoder.yudao.framework.meilisearch.json.MSFiled;
import cn.iocoder.yudao.framework.meilisearch.json.MSIndex;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@MSIndex(uid = "com_baidu_main", primaryKey = "id")
public class MainDO {private Long id;private String seedsName;@MSFiled(openFilter = true, key = "isDelete")private Integer isDelete;@MSFiled(openFilter = true, key = "status")private Integer status;@MSFiled(openFilter = true, key = "classFiledId")private Integer classFiledId;private String classFiledName;@MSFiled(openFilter = true, key = "tags")private List<TageInfo> tags;
}

2.2、集成starter里边的mapper对milisearch进行基本操作

import cn.iocoder.yudao.framework.meilisearch.core.MeilisearchRepository;
import org.springframework.stereotype.Repository;@Repository
public class MeiliSearchMapper extends MeilisearchRepository<MainDO> {
}

2.3、基本使用

@Resource
private MeiliSearchMapper meiliSearchMapper;//根据标签分页查询
SearchRequest searchRequest4 = SearchRequest.builder().limit(pageParam.getPageSize().intValue()).sort(new String[]{"createTime:desc"}).offset(pageParam.getPageNo().intValue() == 0 ? pageParam.getPageNo().intValue() : (pageParam.getPageNo().intValue() - 1) * pageParam.getPageSize().intValue()).filter(new String[]{"tags.id=" + "10010" + " AND status=1 AND isDelete=0"}).build();
SearchResult<MainDO> search4 = meiliSearchMapper.search(searchRequest4);//保存Or编辑
List<SeedsDO> articleCardDTOS = new ArrayList<>();
Boolean aBoolean = meiliSearchMapper.add(articleCardDTOS) > 0 ? Boolean.TRUE : Boolean.FALSE;
//按id删除
meiliSearchMapper.delete(String.valueOf(10085));//根据类目分页查询
SearchRequest searchRequest3 = SearchRequest.builder().limit(pageParam.getPageSize().intValue()).offset(pageParam.getPageNo().intValue() == 0 ? pageParam.getPageNo().intValue() : (pageParam.getPageNo().intValue() - 1) * pageParam.getPageSize().intValue()).build();
StringBuffer sb1 = new StringBuffer();
sb.append("status =1 AND isDelete=0").append(" AND ").append("categoryId =").append(10086L);
searchRequest.setFilter(new String[]{sb.toString()});
searchRequest.setSort(new String[]{"createTime:desc"});
SearchResult<SeedsDO> search3 = meiliSearchMapper.search(searchRequest3);


http://www.ppmy.cn/news/1124906.html

相关文章

ElementUI之首页导航与左侧菜单

目录 一、Mock 1.1 什么是Mock.js 1.2 安装与配置 1.2.1 安装mock.js 1.2.2 引入mock.js 1.3 mock.js使用 1.3.1 定义测试数据文件 1.3.2 mock拦截Ajax请求 1.3.3 界面代码优化 二、总线 2.1 定义 2.2 类型分类 2.3 前期准备 2.4 配置组件与路由关系 2.4.1 配置…

【算法训练-动态规划】一 连续子数组的最大和

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【动态规划】&#xff0c;使用【数组】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&…

Unity之Hololens如何实现传送功能

一.前言 什么是Hololens? Hololens是由微软开发的一款混合现实头戴式设备,它将虚拟内容与现实世界相结合,为用户提供了沉浸式的AR体验。Hololens通过内置的传感器和摄像头,能够感知用户的环境,并在用户的视野中显示虚拟对象。这使得用户可以与虚拟内容进行互动,将数字信…

【JVM】类加载子系统——自问自答

1、类加载的过程&#xff1a; java的类加载过程&#xff0c;是把字节码文件(.class file) 转换到JVM中运行时数据区内的过程。 类加载的过程由 类加载器子系统完成(Class Loader). 字节码文件可以像我们日常开发时在特定文件夹路径下的jar包里&#xff0c;也可以从网络中获取…

【学习笔记】 LGV引理

LGV引理 在一个有向无环图G中&#xff0c;出发点 A { a 1 , a 2 , . . . a n } A\{a_1,a_2,...a_n\} A{a1​,a2​,...an​}&#xff0c;目标点 B { b 1 , b 2 , . . b n } B\{b_1,b_2,..b_n\} B{b1​,b2​,..bn​}。有向边 e e e的权值为 w e w_e we​&#xff0c; e ( u , …

暗月中秋靶场活动writeup

前言 暗月在中秋节搞了个靶场活动&#xff0c;一共有4个flag&#xff0c;本着增长经验的想法参加了本次活动&#xff0c;最终在活动结束的时候拿到了3个flag&#xff0c;后面看了其他人的wp也复现拿到第四个flag。过程比较曲折&#xff0c;所以记录一下。 靶场地址 103.108.…

多层感知机——MLP

源代码在此处&#xff1a;https://github.com/wepe/MachineLearning/tree/master/DeepLearning Tutorials/mlp 一、多层感知机&#xff08;MLP&#xff09;原理简介 多层感知机&#xff08;MLP&#xff0c;Multilayer Perceptron&#xff09;也叫人工神经网络&#xff08;ANN&…

什么是Jmeter ?Jmeter使用的原理步骤是什么?

1.1 什么是 JMeter Apache JMeter 是 Apache 组织开发的基于 Java 的压力测试工具。用于对软件做压力测试&#xff0c;它最初被设计用于 Web 应用测试&#xff0c;但后来扩展到其他测试领域。 它可以用于测试静态和动态资源&#xff0c;例如静态文件、Java 小服务程序、CGI 脚…