用SpringBoot和ElasticSearch实现网盘搜索引擎,附源码,详细教学

最终效果如下

可以扫描小程序码体验,切换到搜索Tabbar。
在这里插入图片描述

小程序端界面实现

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

网页端实现界面

在这里插入图片描述
在这里插入图片描述

后端核心源码

对外提供的api

/**
     * 搜索网盘资源
     *
     * @param condition 条件
     * @return {@link Result< NetDiskSearchDTO >} 网盘资源列表
     */
    @ApiOperation(value = "搜索网盘资源")
    @GetMapping("/netdisks/search")
    public Result<?> listNetDisksBySearch(ConditionVO condition) {
        PageInfo pageInfo = netDiskService.listNetDisksBySearch(condition);
        return Result.ok(pageInfo);
    }

接口声明

 /**
     * 搜索网盘资源
     *
     * @param condition 条件
     * @return 网盘资源列表
     */
    PageInfo listNetDisksBySearch(ConditionVO condition);

接口实现

 @Override
    public PageInfo listNetDisksBySearch(ConditionVO condition) {
        return searchStrategyContext.executeNetDiskSearchStrategy(condition);
    }

执行搜索策略。

提供2种搜索策略,分别是MySQL和ElasticSearch搜索策略。在配置文件进行配置搜索策略。

package top.liuleinet.blog.strategy.context;

import com.github.pagehelper.PageInfo;
import top.liuleinet.blog.dto.NetDiskSearchDTO;
import top.liuleinet.blog.strategy.SearchStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import top.liuleinet.blog.vo.ConditionVO;

import java.util.List;
import java.util.Map;

import static top.liuleinet.blog.enums.SearchModeEnum.getStrategy;

/**
 * 搜索策略上下文
 *
 * @author Liu'Lei
 * @date 2021/07/27
 */
@Service
public class SearchStrategyContext {
    /**
     * 搜索模式
     */
    @Value("${search.mode}")
    private String searchMode;

    @Autowired
    private Map<String, SearchStrategy> searchStrategyMap;

    /**
     * 执行网盘资源搜索策略
     *
     * @param condition 关键字
     * @return {@link List<NetDiskSearchDTO>} 搜索网盘资源
     */
    public PageInfo executeNetDiskSearchStrategy(ConditionVO condition) {
        return searchStrategyMap.get(getStrategy(searchMode)).searchNetDisk(condition);
    }

}

搜索类型枚举

package top.liuleinet.blog.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 搜索类型枚举
 *
 * @author Liu'Lei
 * @date 2021/07/27
 */
@Getter
@AllArgsConstructor
public enum SearchModeEnum {
    /**
     * mysql
     */
    MYSQL("mysql", "mySqlSearchStrategyImpl"),
    /**
     * elasticsearch
     */
    ELASTICSEARCH("elasticsearch", "esSearchStrategyImpl");

    /**
     * 模式
     */
    private final String mode;

    /**
     * 策略
     */
    private final String strategy;

    /**
     * 获取策略
     *
     * @param mode 模式
     * @return {@link String} 搜索策略
     */
    public static String getStrategy(String mode) {
        for (SearchModeEnum value : SearchModeEnum.values()) {
            if (value.getMode().equals(mode)) {
                return value.getStrategy();
            }
        }
        return null;
    }

}

配置文件中的搜索策略相关配置

# 搜索模式 可选 elasticsearch或mysql
search:
  mode: elasticsearch

es搜索策略实现

package top.liuleinet.blog.strategy.impl;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageInfo;
import top.liuleinet.blog.dto.NetDiskSearchDTO;
import top.liuleinet.blog.strategy.SearchStrategy;
import lombok.extern.log4j.Log4j2;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import top.liuleinet.blog.vo.ConditionVO;

import java.util.List;
import java.util.stream.Collectors;

import static top.liuleinet.blog.constant.CommonConst.*;
import static top.liuleinet.blog.enums.NetDiskStatusEnum.OPEN;
/**
 * es搜索策略实现
 *
 * @author Liu'Lei
 * @date 2021/07/27
 */
@Log4j2
@Service("esSearchStrategyImpl")
public class EsSearchStrategyImpl implements SearchStrategy {

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    
    
    @Override
    public PageInfo searchNetDisk(ConditionVO condition) {
        if (StringUtils.isBlank(condition.getKeywords())) {
            return new PageInfo();
        }
        return searchNetdisk(buildNetdiskQuery(condition.getKeywords()), condition);
    }

    /**
     * 搜索网盘资源构造
     *
     * @param keywords 关键字
     * @return es条件构造器
     */
    private NativeSearchQueryBuilder buildNetdiskQuery(String keywords) {
        // 条件构造器
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 根据关键词搜索网盘资源标题
        boolQueryBuilder.must(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("netdiskTitle", keywords)))
                .must(QueryBuilders.termQuery("isDelete", FALSE))
                .must(QueryBuilders.termQuery("isVip",FALSE))
                .must(QueryBuilders.termQuery("status", OPEN.getStatus()));
        nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        return nativeSearchQueryBuilder;
    }

    /**
     * 网盘资源搜索结果高亮
     *
     * @param nativeSearchQueryBuilder es条件构造器
     * @return 搜索结果
     */
    private PageInfo searchNetdisk(NativeSearchQueryBuilder nativeSearchQueryBuilder, ConditionVO condition) {
        // 添加网盘资源标题高亮
        HighlightBuilder.Field titleField = new HighlightBuilder.Field("netdiskTitle");
        titleField.preTags(PRE_TAG);
        titleField.postTags(POST_TAG);
        nativeSearchQueryBuilder.withHighlightFields(titleField);
        // 搜索
        try {
            SearchHits<NetDiskSearchDTO> search = elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), NetDiskSearchDTO.class);
            List<NetDiskSearchDTO> netDiskSearchDTOS = search.getSearchHits().stream().map(hit -> {
                NetDiskSearchDTO netDisk = hit.getContent();
                // 获取标题高亮数据
                List<String> titleHighLightList = hit.getHighlightFields().get("netdiskTitle");
                if (CollectionUtils.isNotEmpty(titleHighLightList)) {
                    // 替换标题数据
                    netDisk.setNetdiskTitle(titleHighLightList.get(0));
                }
                return netDisk;
            }).collect(Collectors.toList());
            Page page = new Page(condition.getCurrent().intValue(), condition.getSize().intValue());
            page.setTotal(netDiskSearchDTOS.size());
            int startIndex = (condition.getCurrent().intValue() - 1) * condition.getSize().intValue();
            int endIndex = Math.min(startIndex + condition.getSize().intValue(),netDiskSearchDTOS.size());
            page.addAll(netDiskSearchDTOS.subList(startIndex,endIndex));
            PageInfo pageInfo = new PageInfo<>(page);
            return pageInfo;
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return new PageInfo();
    }

}


ConditionVO类如下

package top.liuleinet.blog.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;


/**
 * 查询条件
 *
 * @author Liu'Lei
 * @date 2021/07/29
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "查询条件")
public class ConditionVO {

    /**
     * 页码
     */
    @ApiModelProperty(name = "current", value = "页码", dataType = "Long")
    private Long current;

    /**
     * 条数
     */
    @ApiModelProperty(name = "size", value = "条数", dataType = "Long")
    private Long size;

    /**
     * 搜索内容
     */
    @ApiModelProperty(name = "keywords", value = "搜索内容", dataType = "String")
    private String keywords;


    /**
     * 网盘分类
     */
    @ApiModelProperty(name = "category", value = "分类", dataType = "String")
    private String category;

    /**
     * 类型
     */
    @ApiModelProperty(name = "type", value = "类型", dataType = "Integer")
    private Integer type;

    /**
     * 状态
     */
    @ApiModelProperty(name = "status", value = "状态", dataType = "Integer")
    private Integer status;

    /**
     * 是否删除
     */
    @ApiModelProperty(name = "isDelete", value = "是否删除", dataType = "Integer")
    private Integer isDelete;

    /**
     * 是否vip网盘资源
     */
    @ApiModelProperty(name = "isVip", value = "是否vip网盘资源", dataType = "Integer")
    private Integer isVip;
}

小程序端JavaScript代码逻辑

import {
	searchNetdisk
} from '../../services/api'


const app = getApp();

// 在页面中定义插屏广告
let interstitialAd = null;
Page({
	data: {
		StatusBar: app.globalData.StatusBar,
		CustomBar: app.globalData.CustomBar,
		noContentImage: '/images/default/no-data.png',
		historyKeyword: [],
		preRecommendKeyword: [
			'PPT模板', '动漫', 'Java', '狮子王', '万里归途', '阿甘正传', '星际穿越', '满江红', '流浪地球2', '长津湖'
		],
		recommendKeyword: [],
		recommendKeywordView: true,
		title: '资源搜索',
		keywords: '',
		current: 1,
		size: 10,
		loading: false,
		isSearch: false,
		searchResult: {},
		content: [],
		windowHeight: app.globalData.windowHeight
	},

	async onLoad() {
		var that = this;
		var historyKeyword = wx.getStorageSync('netdiskHistory');

		if (historyKeyword) {
			that.setData({
				historyKeyword: historyKeyword
			})
		}
		var preRecommendKeyword = that.data.preRecommendKeyword;
		var recommendKeyword = preRecommendKeyword.slice(0, 5);
		that.setData({
			recommendKeyword: recommendKeyword
		})

		// 在页面onLoad回调事件中创建插屏广告实例
		if (wx.createInterstitialAd) {
			interstitialAd = wx.createInterstitialAd({
				adUnitId: 'adunit-9b1d1f3ffba7cf75'
			})
			interstitialAd.onLoad(() => {})
			interstitialAd.onError((err) => {})
			interstitialAd.onClose(() => {})
		}


	},

	async clickSearch(e) {
		var that = this;
		that.setData({
			keywords: e.currentTarget.dataset.keyword
		})
		await that.search();
	},

	/**
	 * 搜索
	 * @param {*} event
	 */
	async search() {
		var that = this;
		var keywords = that.data.keywords;
		var historyKeywordArray = that.data.historyKeyword;
		if (historyKeywordArray.indexOf(keywords) === -1) {
			historyKeywordArray.unshift(keywords);
			if (historyKeywordArray.length > 10) {
				historyKeywordArray.pop();
			}
			wx.setStorageSync('netdiskHistory', historyKeywordArray);
		}
		that.setData({
			historyKeyword: historyKeywordArray
		})
		await that.getNetdiskList(true, keywords);
		that.setData({
			isSearch: true,
		})
	},

	/**
	 * 搜索输入事件
	 * @param {s} event
	 */
	searchInput(event) {
		var that = this;
		//console.log(event)
		that.setData({
			keywords: event.detail.value,
			isSearch: true
		})
	},
	/**
	 * 清空历史搜索关键字
	 */
	clearHistoryKeyword() {
		var that = this;
		wx.removeStorageSync('netdiskHistory');
		that.setData({
			historyKeyword: []
		})
	},
	changeRecommend() {
		var that = this;
		var arr = that.data.preRecommendKeyword;
		var randomCommendArr = that.getRandomArrayElements(arr, 5);
		that.setData({
			recommendKeyword: randomCommendArr
		})
	},
	changeRecommendView() {
		var that = this;
		that.setData({
			recommendKeywordView: !that.data.recommendKeywordView
		})
	},



	/**
	 * 顶部刷新
	 */
	// async onPullDownRefresh() {
	//   // 显示顶部刷新图标
	//   wx.showNavigationBarLoading()
	//   const that = this
	//   const content = await this.getNetdiskList(true, this.data.keywords)
	//   that.setData({
	//     content: content
	//   })
	//   setTimeout(function () {
	//     // 隐藏导航栏加载框
	//     wx.hideNavigationBarLoading()
	//     // 停止当前页面下拉刷新。
	//     wx.stopPullDownRefresh()
	//   }, 1500)
	// },


	/**
	 * 向下滑动拉去下一页
	 */
	async onReachBottom() {
		var that = this;
		var current = ++that.data.current;
		that.setData({
			current: current,
			content: []
		})
		// 在适合的场景显示插屏广告
		if (interstitialAd && current == 2) {
			interstitialAd.show().catch((err) => {
				console.error(err);
			})
		}
		//console.log(current)
		if (current > that.data.searchResult.pages) {
			wx.showToast({
				title: '已经是最后一页',
			})
		}
		var keyword = that.data.keywords;
		await that.getNetdiskList(false,keyword);
	},

	/**
	 * 向上一页
	 */
	async toPreviousPage() {
		var that = this;
		var current = --that.data.current;
		that.setData({
			current: current
		})
		var keyword = that.data.keywords;
		await that.getNetdiskList(false, keyword);
	},
	/**
	 * 获取网盘资源列表
	 */
	async getNetdiskList(init, keywords) {
		var that = this;
		if (keywords.length == 0) {
			wx.showToast({
				title: '关键词不能为空!',
			})
			return;
		}
		
			if (init) {
				that.initParams();
			}
			var current = that.data.current;
			var size = that.data.size;
			var param = {
				keywords: keywords,
				current: current,
				size: size
			}
			var result = await searchNetdisk(param);
			var resData = result.data;
			var resDataList = resData.list;
			that.setData({
				searchResult: resData,
				content: resDataList
			})
			return;
	},
	/**
	 * 初始化页数和内容
	 */
	initParams() {
		var that = this;
		that.setData({
			searchResult: {},
			content: [],
			current: 1
		})
	},
	toHome() {
		var that = this;
		that.setData({
			isSearch:false
		})
	},
	// backPre() {
	//   this.setData({
	//     isSearch: !this.data.isSearch
	//   })
	// },

	/**
	 * 数组中随机取几个元素
	 * @param {*} arr 来源数组
	 * @param {*} count 取数量
	 */
	getRandomArrayElements(arr, count) {
		var shuffled = arr.slice(0),
			i = arr.length,
			min = i - count,
			temp, index;
		while (i-- > min) {
			index = Math.floor((i + 1) * Math.random());
			temp = shuffled[index];
			shuffled[index] = shuffled[i];
			shuffled[i] = temp;
		}
		return shuffled.slice(min);
	},

	/**
	 * 分享
	 * @param {*} res
	 */
	onShareAppMessage: function (res) {
		var that = this;
		return {
			title: that.data.keywords + '!,点击打开',
			path: '/pages/netdisk/index'
		}
	},
	onShareTimeline: function (res) {
		var that = this;
		return {
			title: that.data.keywords + '!,点击打开'
		}
	}
})
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值