优势
- 仅需要500M内存即可使用
- ms级响应
- 简单易上手的Api &快速启动。
部署
docker run -it --rm \
-p 7700:7700 \
-e MEILI_MASTER_KEY='秘钥'\
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:v1.0
Ui 访问地址 http://ip:7700
接入
<!-- meilisearch 轻量级搜索 -->
<!-- https://mvnrepository.com/artifact/com.meilisearch.sdk/meilisearch-java -->
<dependency>
<groupId>com.meilisearch.sdk</groupId>
<artifactId>meilisearch-java</artifactId>
<version>0.10.0</version>
</dependency>
代码(直接复制)
MSIndex --索引注解
package com.aurora.annotation;
import java.lang.annotation.*;
/**
* @author cgy
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MSIndex {
/**
* 索引
*/
String uid() default "";
/**
* 主键
*/
String primaryKey() default "";
/**
* 分类最大数量
*/
int maxValuesPerFacet() default 100;
/**
* 单次查询最大数量
*/
int maxTotalHits() default 1000;
}
MSFiled --索引字段注解
package com.aurora.annotation;
import java.lang.annotation.*;
/**
* MeiliSearch 字段注解
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MSFiled {
/**
* 是否开启过滤
*/
boolean openFilter() default false;
/**
* 是否不展示
*/
boolean noDisplayed() default false;
/**
* 是否开启排序
*/
boolean openSort() default false;
/**
* 处理的字段名
*/
String key() ;
}
MeiliSearchAutoConfiguration
package com.aurora.meilisearch.config;
import com.meilisearch.sdk.Client;
import com.meilisearch.sdk.Config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author cgy
* 2020/1/15 15:53
*/
@Data
@Configuration
public class MeiliSearchAutoConfiguration {
@Value("${meiliSearch.hostUrl}")
private String hostUrl;
@Value("${meiliSearch.apiKey}")
private String apiKey;
@Bean
@ConditionalOnMissingBean(Client.class)
Client client() {
return new Client(config());
}
@Bean
@ConditionalOnMissingBean(Config.class)
Config config() {
return new Config(hostUrl, apiKey);
}
}
MeilisearchRepository -索引初始化&增删改查
package com.aurora.meilisearch.service;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.aurora.annotation.MSFiled;
import com.aurora.annotation.MSIndex;
import com.aurora.meilisearch.json.JsonHandler;
import com.aurora.meilisearch.json.SearchResult;
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.*;
/**
* @author cgy
*/
public class MeilisearchRepository<T> implements InitializingBean, DocumentOperations<T> {
private Index index;
private Class<T> tClass;
private JsonHandler jsonHandler = new JsonHandler();
@Resource
private Client client;
@Override
public T get(String identifier) {
T document;
try {
document = index.getDocument(identifier, tClass);
} catch (Exception e) {
throw new RuntimeException(e);
}
return document;
}
@Override
public 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;
}
@Override
public 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;
}
@Override
public 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;
}
@Override
public int add(T document) {
List<T> list = Collections.singletonList(document);
return add(list);
}
@Override
public int update(T document) {
List<T> list = Collections.singletonList(document);
return update(list);
}
@Override
public int add(List documents) {
int taskId;
try {
taskId = index.addDocuments(JSON.toJSONString(documents)).getTaskUid();
} catch (Exception e) {
throw new RuntimeException(e);
}
return taskId;
}
@Override
public int update(List documents) {
int updates;
try {
updates = index.updateDocuments(JSON.toJSONString(documents)).getTaskUid();
} catch (Exception e) {
throw new RuntimeException(e);
}
return updates;
}
@Override
public int delete(String identifier) {
int taskId;
try {
taskId = index.deleteDocument(identifier).getTaskUid();
} catch (Exception e) {
throw new RuntimeException(e);
}
return taskId;
}
@Override
public int deleteBatch(String... documentsIdentifiers) {
int taskId;
try {
taskId = index.deleteDocuments(Arrays.asList(documentsIdentifiers)).getTaskUid();
} catch (Exception e) {
throw new RuntimeException(e);
}
return taskId;
}
@Override
public int deleteAll() {
int taskId;
try {
taskId = index.deleteAllDocuments().getTaskUid();
} catch (Exception e) {
throw new RuntimeException(e);
}
return taskId;
}
@Override
public 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);
}
@Override
public SearchResult<T> search(String q, int offset, int limit) {
SearchRequest searchRequest = new SearchRequest();
searchRequest.setQ(q);
searchRequest.setOffset(offset);
searchRequest.setLimit(limit);
return search(searchRequest);
}
@Override
public 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);
}
@Override
public Settings getSettings() {
try {
return index.getSettings();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public TaskInfo updateSettings(Settings settings) {
try {
return index.updateSettings(settings);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public TaskInfo resetSettings() {
try {
return index.resetSettings();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Task getUpdate(int updateId) {
try {
return index.getTask(updateId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public 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);
}else {
client.createIndex(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);
}
}
继承MeilisearchRepository 即可 T 是你要做存储的文档实体类 我这里用的是 **ArticleCardDTO
**
import com.aurora.meilisearch.service.MeilisearchRepository;
import org.springframework.stereotype.Repository;
@Repository
public class MeiliSearchMapper extends MeilisearchRepository<T> {
}
使用方法
配置
实体类配置
package com.aurora.dto;
import com.aurora.annotation.MSFiled;
import com.aurora.annotation.MSIndex;
import com.aurora.entity.Tag;
import com.aurora.entity.UserInfo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@MSIndex(uid="articles",primaryKey="id")
public class ArticleCardDTO {
private Integer id;
private String articleCover;
@MSFiled(openFilter = true,key="articleTitle")
private String articleTitle;
private String articleSubheading;
private String articleContent;
@MSFiled(openFilter = true,key="isTop")
private Integer isTop;
@MSFiled(openFilter = true,key="isFeatured")
private Integer isFeatured;
@MSFiled(openFilter = true,key="isDelete")
private Integer isDelete;
@MSFiled(openFilter = true,key="categoryId")
private Integer categoryId;
private UserInfo author;
private String categoryName;
@MSFiled(openFilter = true,key="tags.id")
private List<Tag> tags;
@MSFiled(openFilter = true,key="status")
private Integer status;
@Field(type = FieldType.Long)
@MSFiled(openSort = true,key="createTime")
private Long createTime;
@MSFiled(openSort = true,key = "updateTime")
@Field(type = FieldType.Long)
private Long updateTime;
}
配置文件配置
meiliSearch:
hostUrl: 访问地址
apiKey: 启动的时候设置的秘钥
使用
简单查询(首页的搜索-根据关键字查询文章)
StringBuffer sb=new StringBuffer();
StringBuffer articleTitle = sb
.append("status=1")
.append(" AND ")
.append("isDelete=0");
SearchRequest searchRequest=new SearchRequest()
.setFilter(new String[]{articleTitle.toString()})
.setQ(keywords)
.setLimit(10);
SearchResult<ArticleCardDTO> search = meiliSearchMapper.
search(searchRequest);
多条件查询(查询置顶和推荐)
SearchRequest searchRequest=new SearchRequest();
searchRequest.setFilter(new String[]{"(isFeatured =1 AND (status=1 AND isDelete=0) ) OR ( isTop=1 AND (status=1 AND isDelete=0)) "});
searchRequest.setLimit(10);
SearchResult<ArticleCardDTO> search = meiliSearchMapper.search(searchRequest);
分页查询全部文章并创建时间倒序
SearchRequest searchRequest = new SearchRequest();
searchRequest.setLimit(PageUtils.getSize().intValue());
searchRequest.setOffset(PageUtils.getCurrent().intValue() == 0 ? PageUtils.getCurrent().intValue() : (PageUtils.getCurrent().intValue() - 1) * PageUtils.getSize().intValue());
searchRequest.setFilter(new String[]{"status =1 AND isDelete=0"});
searchRequest.setSort(new String[]{"createTime:desc"});
SearchResult<ArticleCardDTO> search = meiliSearchMapper.search(searchRequest);
根据类目分页查询文章
SearchRequest searchRequest = new SearchRequest();
searchRequest.setLimit(PageUtils.getSize().intValue());
searchRequest.setOffset(PageUtils.getCurrent().intValue() == 0 ? PageUtils.getCurrent().intValue() : (PageUtils.getCurrent().intValue() - 1) * PageUtils.getSize().intValue());
StringBuffer sb = new StringBuffer();
sb.append("status =1 AND isDelete=0").append(" AND ").append("categoryId =").append(categoryId);
searchRequest.setFilter(new String[]{sb.toString()});
searchRequest.setSort(new String[]{"createTime:desc"});
SearchResult<ArticleCardDTO> search = meiliSearchMapper.search(searchRequest);
根据标签分页查询文章
SearchRequest searchRequest = new SearchRequest();
searchRequest.setLimit(PageUtils.getSize().intValue());
searchRequest.setSort(new String[]{"createTime:desc"});
searchRequest.setOffset(PageUtils.getCurrent().intValue() == 0 ? PageUtils.getCurrent().intValue() : (PageUtils.getCurrent().intValue() - 1) * PageUtils.getSize().intValue());
searchRequest.setFilter(new String[]{"tags.id=" + tagId + " AND status=1 AND isDelete=0"});
SearchResult<ArticleCardDTO> search = meiliSearchMapper.search(searchRequest);
保存Or编辑文章
meiliSearchMapper.add(articleCardDTOS)>0?Boolean.TRUE:Boolean.FALSE;
删除文章
meiliSearchMapper.delete(String.valueOf(id));
最终效果
ui效果
展示效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/4dce7e51d331485bb9d8e3171ef6b41b.png
更多教程
请关注针芒科技