基于数据库的全文检索实现

news2025/3/13 7:15:50

对于内容摘要,信件内容进行全文检索
基于SpringBoot 2.5.6+Postgresql+jpa+hibernate实现

依赖

<spring-boot.version>2.5.6</spring-boot.version>
<hibernate-types-52.version>2.14.0</hibernate-types-52.version>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- hibernate支持配置 -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
</dependency>
<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types-52.version}</version>
</dependency>
<!-- hibernate支持配置 -->
 
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

业务逻辑

登记保存之后,处理完成业务逻辑,发送全文检索事件

//附加类型对应的附件ids
Map<String, List<String>> attCategoryToAttIds = new HashMap<String, List<String>>();
attCategoryToAttIds.put(cmpRecord.getFileCategory(), files==null?null:files.stream().map(d->d.getId()).collect(Collectors.toList()));
//处理监听事件所需要的数据
Map<String,Object>eventData = Utils.buildMap("recordId", cmpRecord.getId(),"newRecord", true,"attCategoryToAttIds", attCategoryToAttIds);
//创建全文检索事件
DomainEvent de = new DefaultDomainEvent(cmpRecord.getId() + "_Handle_CmpRecord_FullTextSearch", operateInfo, ExecutePoint.CURR_THREAD,
                eventData, new Date(), "Handle_CmpRecord_FullTextSearch");
//发布事件
DomainEventPublisherFactory.getRegisteredPublisher().publishEvent(de);

处理业务发送全文检索事件

@Service
@Transactional
@SuppressWarnings("unchecked")
public class HandleCmpRecordFullTextSearchListener implements IDomainEventListener {
    
    @Autowired
    private CmpRecordRepository cmpRecordRepository;
    @Autowired
    private DataChangeLogEventRepository dataChangeLogEventRepository;
    
    @Override
    public void onEvent(DomainEvent event) {
        AccessTokenUser operator=event.getOperator();
        Date operateTime=event.obtainEventTime();
        Map<String,Object> otherData=(Map<String,Object>)event.getEventData();
        String recordId = (String) otherData.get("recordId");
        boolean newRecord=(boolean)otherData.get("newRecord");
        String comment = (String) otherData.get("comment");//办理记录的备注
        if(StringUtils.isBlank(recordId)) {
            throw new RuntimeException("未指定信访记录id");
        }
     	//获取登记信息
        CmpRecord cmdRecord = cmpRecordRepository.getCmpRecordById(recordId);
        //指定关联关系
        RelateProjValObj cmpRdProj=new RelateProjValObj(recordId,RelateProjConstants.PROJTYPE_CMP_RECORD); 
        //这是关联那个业务
        List<RelateProjValObj> mainProjs=Arrays.asList(cmpRdProj);
        DomainEvent de=null;
        //登记信息是无效的 则删除已存在的和这个件相关的
        if(cmdRecord==null||!cmdRecord.isValidEntity()) {
            //删除全文检索信息
            de=new FullTextSearchOperateEvent(recordId+"_FullTextSearch_Remove", null, operator, operateTime,
                    mainProjs, null);
            DomainEventPublisherFactory.getRegisteredPublisher().publishEvent(de);
            return;
        }
        //全文检索 类型前缀 
        String contentTypepPefix=RelateProjConstants.PROJTYPE_CMP_RECORD;
        //在当前线程中执行,保证事务一致性
        ExecutePoint executePoint=ExecutePoint.CURR_THREAD;
        
        /***********************************************关键词检索-内容摘要***********************************************/
        //全文检索的类型 区分内容摘要 附件内容
        List<String> contentTypes=Arrays.asList(contentTypepPefix+"_contentAbstract");
        String contentAbstract =cmdRecord.getBaseInfo().getContentAbstract();//内容摘要
        if(StringUtils.isBlank(contentAbstract)) contentAbstract="";
        if(StringUtils.isNotBlank(comment)) {
            if(StringUtils.isNotBlank(contentAbstract)) contentAbstract=contentAbstract + ",";
            contentAbstract=contentAbstract+comment;
        }
        de=new FullTextSearchOperateEvent(recordId+"_FullTextSearch_Update", executePoint, operator, operateTime,
                mainProjs, contentTypes, contentAbstract, null);
        DomainEventPublisherFactory.getRegisteredPublisher().publishEvent(de);
        
        /***********************************************关键词检索-信件内容***********************************************/
        contentTypes=Arrays.asList(contentTypepPefix+"_content");
        String content =cmdRecord.getBaseInfo().getContent();//信件内容
        de=new FullTextSearchOperateEvent(recordId+"_FullTextSearch_Update", executePoint, operator, operateTime,
                mainProjs, contentTypes, content, null);
        DomainEventPublisherFactory.getRegisteredPublisher().publishEvent(de);
        
        /***********************************************关键词检索-附件(原信等)***********************************************/
        //如果附件也需要检索  设置attIds参数
        Map<String,List<String>> attCategoryToAttIds=(Map<String,List<String>>)otherData.get("attCategoryToAttIds");
        if(attCategoryToAttIds!=null && attCategoryToAttIds.size() > 0) {
            //按附件类型分开
            for (Map.Entry<String,List<String>> d : attCategoryToAttIds.entrySet()) {
                contentTypes=Arrays.asList(contentTypepPefix+"_att_"+d.getKey());
                List<String> attIds=d.getValue();//公文相关附件
                de=new FullTextSearchOperateEvent(recordId+"_att_"+d.getKey()+"_FullTextSearch_Update", executePoint,
                        operator, operateTime, mainProjs, contentTypes, null, attIds);
                DomainEventPublisherFactory.getRegisteredPublisher().publishEvent(de);
            }
        }
    }
    
    @Override
    public boolean listenOn(String eventType) {
        return "Handle_CmpRecord_FullTextSearch".equals(eventType);
    }
}

统一处理全文检索事件

@Service
@Transactional
public class FullTextSearchListener extends JpaHibernateRepository implements IDomainEventListener{
    
	@Autowired
	private FullTextSearchRepository fullTextSearchRepository;
	@Autowired
	private IFileSysService fileSysService;
	
    @Override
    public void onEvent(DomainEvent event) {
    	if("true".equals(BaseConstants.getProperty("prefetchingRecordNo", "false"))){
    		return;
    	}
        FullTextSearchOperateEvent de = null;
        if(event instanceof FullTextSearchOperateEvent) {
        	de=(FullTextSearchOperateEvent)event;
        }
        if(de==null) {
        	return;
        }
        if(FullTextSearchOperateEvent.EVENTTYPE_UPDATE.equals(de.getEventType())) {
        	/**
        	 "mainProjs":List<RelateProjValObj> 必选
        	 "contentType":String 必选
        	 "content":String 可选
        	 "attIds":List<String> 可选  content与attIds都不存在 会删除对应关键词检索
        	 "relProjs":List<RelateProjValObj> 可选  指定的需要添加的关系
        	 "removeOtherRelProjs":false  可选  是否清除 指定relProjs以外的关联记录
        	 */
        	this.fullTextSearchUpdate(de);
        }else if(FullTextSearchOperateEvent.EVENTTYPE_REMOVE.equals(de.getEventType())) {
        	/**
        	 "mainProjs":List<RelateProjValObj> 必选
        	 */
        	this.fullTextSearchRemoveByProjs(de);
        }
    }
    
	//关键词检索增加
	private void fullTextSearchUpdate(FullTextSearchOperateEvent de) {
		Date date=de.obtainEventTime();
		if(date==null) {
			date=new Date();
		}
		List<RelateProjValObj> mainProjs=de.getMainProjs();
		String contentType=null;
		if(de.getContentTypes()!=null&&de.getContentTypes().size()==1) {
			contentType=de.getContentTypes().get(0);
		}
		String content=de.getContent();
		List<String> attIds=de.getAttIds();
		if(mainProjs==null||mainProjs.size()==0
				||StringUtils.isBlank(contentType)
				) {
			throw new RuntimeException("数据指定错误");
		}
		Set<String> fullTextIds=new HashSet<String>();
		for (RelateProjValObj mainProj : mainProjs) {
			if(StringUtils.isBlank(mainProj.getProjId())||StringUtils.isBlank(mainProj.getProjType())) {
				continue;
			}
			fullTextIds.add(new FullTextSearch(mainProj,contentType,null,null).getId());
		}
		if(fullTextIds.size()==0) {
			throw new RuntimeException("数据指定错误");
		}
		//这是从附件中获取文本数据
		if(StringUtils.isBlank(content)&&attIds!=null) {
			content="";
			try {
				if(attIds.size()>0) {
					Map<String,String> attIdToContentMao=ThreadLocalCache.fetchAPIData(null,()->{
						return fileSysService.findFileContentByIds(attIds, true);
					});
					for (String attContent : attIdToContentMao.values()) {
						if(StringUtils.isBlank(attContent)) {
							continue;
						}
						if(StringUtils.isNotBlank(content)) {
							content+=",";
						}
						content+=RegExUtils.replaceAll(attContent, "\\u0000", "");//处理掉非法字符
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		//从数据库中获取已经存的
		List<FullTextSearch> oldFullTexts=this.fullTextSearchRepository.findFullTextSearchByIds(fullTextIds);
		Map<String,FullTextSearch> oldFullTextMap=oldFullTexts.stream().collect(Collectors.toMap(d->d.getId(),d->d));
		//遍历这次需要更新的记录
		for (RelateProjValObj mainProj : mainProjs) {
			if(StringUtils.isBlank(mainProj.getProjId())||StringUtils.isBlank(mainProj.getProjType())) {
				continue;
			}
			FullTextSearch fullText=new FullTextSearch(mainProj, contentType, content, date);
			
			FullTextSearch oldFullText=oldFullTextMap.get(fullText.getId());
			//旧的记录中已存在 则更新
			if(oldFullText!=null) {
				if(StringUtils.isBlank(content)) {
				//如果内容未空 则删除	
				this.fullTextSearchRepository.removeFullTextSearch(oldFullText);
					return;
				}
				//如果存在内容,则更新
				this.fullTextSearchRepository
				.updateFullTextSearchContent(fullText.getId(), content, date);
			}else {
				if(StringUtils.isBlank(content)) {
					return;
				}
				try {//否则 创建全文检索记录
					this.fullTextSearchRepository.createFullTextSearch(fullText);
				} catch (Exception e) {
					e.printStackTrace();
					return;
				}
			}
			
		}
	}
	
	//关键词检索删除  根据主相关件
	private void fullTextSearchRemoveByProjs(FullTextSearchOperateEvent de) {
		Date date=de.obtainEventTime();
		if(date==null) {
			date=new Date();
		}
		List<RelateProjValObj> mainProjs=de.getMainProjs();
		if(mainProjs==null||mainProjs.size()==0) {
			throw new RuntimeException("数据指定错误");
		}
		
		List<String> projKeys=new ArrayList<String>();
		for (RelateProjValObj mainProj : mainProjs) {
			projKeys.add(mainProj.getProjKey());
		}
		Map<String,Object> params=new HashMap<String,Object>();
		StringBuffer hql=new StringBuffer();
		hql.append("delete from ").append(FullTextSearch.class.getName()).append(" ");
		hql.append("where mainProj.projKey IN(:projKeys) ");
		params.put("projKeys", projKeys);
		if(de.getContentTypes()!=null&&de.getContentTypes().size()>0) {
			params.put("contentTypes", de.getContentTypes());
		}
		this.createHQLQueryByMapParams(hql.toString(), params).executeUpdate();
	}
	
	@Override
    public boolean listenOn(String eventType) {
        return eventType.startsWith(FullTextSearchOperateEvent.class.getName());
    }
}

全文检索实体

@Entity
@Table(
    name="TV_FULLTEXT_SEARCH",
    indexes={
        @Index(name="idx_TV_FULLTEXT_SEARCH1",columnList="projKey"),
        @Index(name="idx_TV_FULLTEXT_SEARCH2",columnList="contentType")
    }
)
public class FullTextSearch extends IEntity {
	
    @Id
    @Column(length=200)
    private String id;
    private RelateProjValObj mainProj;//来源相关件
    @Lob
    @Type(type="org.hibernate.type.TextType")
    private String content;//检索内容
    @Column(length=100)
    private String contentType;//检索类型
    @Column(length=100)
    private Date lastUpdateDate;//最后更新时间
    
    
    public String getId() {
        return id;
    }
    public String getContent() {
        return content;
    }
    public String getContentType() {
        return contentType;
    }
    public RelateProjValObj getMainProj() {
        return mainProj;
    }
    public Date getLastUpdateDate() {
		return lastUpdateDate;
	}
	
	
	public FullTextSearch() {
    }
    public FullTextSearch(RelateProjValObj mainProj, String contentType,
    		String content, Date lastUpdateDate) {
        this.id = mainProj.getProjKey()+"_"+contentType;
        this.mainProj = mainProj;
        this.content = content;
        this.contentType = contentType;
        this.lastUpdateDate = lastUpdateDate;
        if(this.lastUpdateDate==null){
            this.lastUpdateDate = new Date();
        }
    }

}

存储数据格式

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

查询

sql大致就是这样的逻辑

select tv.id from tv_cmp_dw_query tv join tv_fulltext_search tvs on tv.id = tvs.proj_id where tvs.contet_type in () and conent like '%测试%'

事件处理机制请看另一篇文章
自定义事件处理机制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1518775.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Chrome浏览器滚动条样式优化

针对Chrome浏览器&#xff0c;可以全局设置滚动条样式&#xff0c;让你的项目更美观 ::-webkit-scrollbar-track-piece {background-color: transparent; } ::-webkit-scrollbar {width: 7px;height: 7px;background-color: transparent; }::-webkit-scrollbar-thumb {border-…

ctfshow(WEB AK赛)

目录 web-观己 web1-观字 web2-观星 web3-观图 web4-观心 过程and分析 web-观己 没啥难的有include 想着伪协议 但是过滤了php 那就是用file为协议读取本地文件 全靠猜 <?php if(isset($_GET[file])){$file $_GET[file];if(preg_match(/php/i, $file)){die(error);}…

【深度学习笔记】9_5 多尺度目标检测

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 9.5 多尺度目标检测 在9.4节&#xff08;锚框&#xff09;中&#xff0c;我们在实验中以输入图像的每个像素为中心生成多个锚框。这些…

opencv中的图像高斯双边模糊—bilateralFilter函数

高斯双边滤波&#xff08;Bilateral Filtering&#xff09;是一种非线性的滤波方法&#xff0c;用于平滑图像&#xff0c;同时保留边缘。与传统的高斯模糊不同&#xff0c;双边滤波在平滑图像的同时&#xff0c;能够避免模糊边缘。这是通过考虑像素值的差异来实现的&#xff1a…

把 Windows 装进 Docker 容器里

本篇文章聊聊如何在 Docker 里运行 Windows 操作系统&#xff0c; Windows in Docker Container&#xff08;WinD&#xff09;。 写在前面 我日常使用 macOS 和 Ubuntu 来学习和工作&#xff0c;但是时不时会有 Windows 使用的场景&#xff0c;不论是运行某个指定的软件&…

YOLOv8_pose-Openvino和ONNXRuntime推理【CPU】

纯检测系列&#xff1a; YOLOv5-Openvino和ONNXRuntime推理【CPU】 YOLOv6-Openvino和ONNXRuntime推理【CPU】 YOLOv8-Openvino和ONNXRuntime推理【CPU】 YOLOv7-Openvino和ONNXRuntime推理【CPU】 YOLOv9-Openvino和ONNXRuntime推理【CPU】 跟踪系列&#xff1a; YOLOv5/6/7-O…

Fix a Tree(树的遍历,判断是否有环 并连成一颗树 )

题意翻译 对于下图中的树&#xff0c; 可以用数组表示为 [2,3,3,2]。这种可以表示树的数组&#xff08;即有效&#xff09;需要符合以下条件&#xff1a; 有且只有一个索引 r &#xff0c;符合pr​r 。其中顶点 r 是树的根。对于所有剩下的 n−1 个顶点 i 一定要有在 i 和 pi…

怎么制作自己的微信小程序店铺?

移动互联网的迅猛发展&#xff0c;微信小程序已成为商家拓展线上业务的重要工具。它不仅能够提供便捷的用户访问体验&#xff0c;还能够帮助商家快速构建起一个功能齐全的在线商城。那么&#xff0c;商家怎么制作自己的微信小程序店铺&#xff1f; 一、准备工作&#xff1a; …

鸿蒙 Harmony 初体验

前言 看现在网上传得沸沸扬扬的鸿蒙&#xff0c;打算弄个 hello world 玩一下, 不然就跟不上时代的发展了 环境安装 我的环境 Windows 11 家庭中文版HarmonyOS SDK (API 9)DevEco Studio (3.1.1 Release)Node.js (16.19.1) 开发IDE下载 官方下载链接 配置 nodejs 这里帮…

MWC 2024|「Paraverse平行云」展示空间计算时代沉浸式交互体验

&#x1f389;当地时间2月26日&#xff0c;2024年世界移动通信大会&#xff08;MWC2024&#xff09;在巴塞罗那拉开帷幕。作为全球移动通信领域最大的技术展会之一&#xff0c;MWC被视为全球通信行业风向标。 &#x1f680;随着Vision Pro再次点燃全球空间计算技术热情&#xf…

电脑文件msvcr100.dll丢失的多种解决方法,快速修复dll报错问题

当计算机用户遇到“msvcr100.dll丢失”的问题时&#xff0c;可能会感到困扰并急于寻求解决方案。这个提示通常意味着系统中某个关键的动态链接库文件缺失&#xff0c;这可能导致某些应用程序无法正常启动或运行。msvcr100.dll是Microsoft Visual C Redistributable Package的一…

HarmonyOS应用开发-Stage模型开发概述

基本概念 UI框架 HarmonyOS提供了一套UI开发框架&#xff0c;即方舟开发框架&#xff08;ArkUI框架&#xff09;。提供了应用UI开发所必需的能力&#xff1a;多种组件、布局计算、动画能力、UI交互、绘制。 方舟开发框架针对开发者提供了两种开发范式&#xff1a; 基于ArkTS…

Java实现知乎热点小时榜爬虫

1.效果演示 1.1 热点问题列表 启动程序后&#xff0c;自动展示热点问题&#xff0c;并等待终端输入 1.2 根据序号选择想看的热点问题 输入问题序号&#xff0c;展示回答内容 1.3 退出 输入q即可退出程序 2.源码 2.1 pom.xml <?xml version"1.0" enco…

【机器学习】无监督学习算法之:层次聚类

层次聚类 1、引言2、层次聚类2.1 定义2.2 原理2.3 实现方式2.4 算法公式2.5 代码示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 这周末过的滋润啊。 小鱼&#xff1a;… 每个周末都挺滋润的啊。 小屌丝&#xff1a;啊~ ~ 你这… 小鱼&#xff1a;周末加班&#xf…

从大厂到高校,鸿蒙人才“红透半边天”!

截至目前&#xff0c;继清华大学、北京航空航天大学、武汉大学等985高校开设鸿蒙相关课程后&#xff0c;已经或将要开设鸿蒙相关课程的985、211高校达到近百所&#xff0c;为鸿蒙人才培养提供沃土。 随着鸿蒙系统即将摒弃安卓&#xff0c;鸿蒙原生应用将全面启动的背景下&…

win10从Huggingface下载模型

这里写自定义目录标题 安装CLI工具设置环境变量下载 安装CLI工具 安装Huggingface CLI pip install -U huggingface_hub设置环境变量 设置好变量后&#xff0c;重新启动一个新的命令窗口&#xff0c;cmd或者powershell 下载 huggingface-cli download --resume-download fa…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的安全帽检测系统(深度学习模型+UI界面代码+训练数据集)

摘要&#xff1a;开发先进的安全帽识别系统对提升工作场所的安全性至关重要。本文详细介绍了使用深度学习技术创建此类系统的方法&#xff0c;并分享了完整的实现代码。系统采用了强大的YOLOv8算法&#xff0c;并对其与YOLOv7、YOLOv6、YOLOv5的性能进行了详细比较&#xff0c;…

mysql数据库:使用 bash脚本 + 定时任务 自动备份数据

mysql数据库&#xff1a;使用 bash脚本 定时任务 自动备份数据 1、前言2、为什么需要自动化备份&#xff1f;3、编写备份脚本4、备份脚本授权5、添加定时任务6、重启 crond / 检查 crond 服务状态7、备份文件检查 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏…

pytorch(十)循环神经网络

文章目录 卷积神经网络与循环神经网络的区别RNN cell结构构造RNN例子 seq2seq 卷积神经网络与循环神经网络的区别 卷积神经网络&#xff1a;在卷积神经网络中&#xff0c;全连接层的参数占比是最多的。 卷积神经网络主要用语处理图像、语音等空间数据&#xff0c;它的特点是局部…

【分类讨论】【解析几何】【 数学】【推荐】1330. 翻转子数组得到最大的数组值

作者推荐 视频算法专题 本文涉及知识点 分类讨论 解析几何 LeetCode1330. 翻转子数组得到最大的数组值 给你一个整数数组 nums 。「数组值」定义为所有满足 0 < i < nums.length-1 的 |nums[i]-nums[i1]| 的和。 你可以选择给定数组的任意子数组&#xff0c;并将该子…