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

对于内容摘要,信件内容进行全文检索
基于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.mfbz.cn/a/459119.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

挂耳式蓝牙耳机哪家的好用?一次搞定的全方位选购攻略

对于那些在锻炼时也不忘享受旋律的朋友们&#xff0c;我要透露挂耳式蓝牙耳机的魔力&#xff01;这种耳机实在是太棒了&#xff0c;我猜很多同好都跟我一样&#xff0c;在做运动时偏爱有音乐相伴&#xff0c;以点燃我们的运动激情。但使用传统入耳式蓝牙耳机跑步时&#xff0c;…

综合利用Cisco Packet Tracer模拟器配置园区网

1. 内容 1.在课室交换机中创建各个课室的VLAN&#xff0c;并将1-20端口平均分配给各个课室。 2.使用课室交换机的每个端口只能接入一台计算机&#xff0c;发现违规就丢弃未定义地址的包。3.网络内部使用DHCP分配各课室的IP地址&#xff0c;在课室交换机按照第一题划分的VLAN地…

ThinkBook 14 G3 ITLC(21A3)原厂Win11系统下载,恢复开箱预装oem系统

lenovo联想ThinkBook 14 G3 笔记本电脑原装出厂Windows11系统镜像安装包 链接&#xff1a;https://pan.baidu.com/s/1MZj2Fm7NYUsCwcT9pFGb8Q?pwdajf0 提取码&#xff1a;ajf0 联想笔记本原装系统自带所有驱动、出厂主题壁纸、系统属性联机支持标志、Office办公软件、联想…

【Linux】Centos7安装Nginx1.21.6

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件( IMAP/POP3)代理服务器。其特点是占有内存少&#xff0c;并发能力强&#xff0c;事实上nginx的并发能力在同类型的网页服务器中表现较好&#xff0c;中国大陆使nginx的网站有:百度、京东、新浪、网易、腾讯、淘宝等。 …

蓝桥杯单片机快速开发笔记——HC573/HC138

一、原理分析 二、思维导图 三、代码参考 #include "HC573.h" #include "reg52.h"void Set_HC573(unsigned char channel, unsigned char dat) {P2 (P2 & 0x1f) | 0x00; //赋值之前&#xff0c;关闭全部锁存器P0 dat; //保存待设置…

钉钉小程序 - - - - - 如何通过一个链接打开小程序内的指定页面

方式1 钉钉小程序 scheme dingtalk://dingtalkclient/action/open_mini_app?miniAppId123&pagepages%2Findex%2Findex%3Fx%3D%25E4%25B8%25AD%25E6%2596%2587 方式2 https://applink.dingtalk.com/action/open_mini_app?type2&miniAppIdminiAppId&corpIdcorpId&…

【Git】error: bad signature 0xb86f1e1 和 bfatal: index file corrupt

一、问题 之前都好好的&#xff0c;今天执行 git add .的时候突然报错 报错原因翻译成中文&#xff1a;索引文件损坏 二、解决方法 方法1&#xff1a; 删除.git隐藏文件夹中的index文件 然后执行 git reset 重新生成index文件 git reset 方法2&#xff1a; 重新从远程克隆…

css之常用样式

展示样式一&#xff1a; <div class"showListBox"><div class"List" v-for"(i,index) in sealList" :key"index"> <div class"ListItemCon"><div class"ListItem-titleBox"><img src…

沉浸式感受旧时光,VR全景让游客都爱上老街区打卡地

近年来&#xff0c;随着城市建设的推进&#xff0c;很多老建筑以及周边的道路都发生了很大的变化&#xff0c;为了让更多的游客可以领略城市发展的进程以及旧时的人文风情&#xff0c;很多城市都会通过实地场景拍摄制作VR全景&#xff0c;将老街区、老建筑的真实场景进行虚拟再…

「SpringBrick快速入门指南」:☀️ 后端领域新兴技术璀璨之星☀️ 基于Spring Boot的高级插件化开发框架

文章目录 关于 | About技术文档 | Document开源项目 | Project 案例 | Demo项目结构 | Structure主程序配置集成 | Settings引入框架依赖 | Framework在配置文件加入配置 | YamlSpringBoot启动类改引导类 | Change 插件配置集成 | Settings引入依赖 | XML定义插件引导类 | Clas…

Python轴承故障诊断 (15)基于CNN-Transformer的一维故障信号识别模型

目录 往期精彩内容&#xff1a; 前言 1 轴承数据加载与预处理 1.1 导入数据 1.2 数据预处理&#xff0c;制作数据集 3 基于Pytorch的CNN-Transfromer轴承故障诊断分类 3.1 定义CNN-Transfromer分类网络模型 3.2 设置参数&#xff0c;训练模型 3.3 模型评估 代码、数据…

openstack(T)启动实例状态为错误,如何解决

---基本服务得是正常的 ---1.在web界面看是什么错误 点击你的实例名称&#xff0c;在概况里面去查看 当时我的error &#xff1a;编码500 消息 No valid host was found. 错误原因 1&#xff1a;资源不足 2&#xff1a;未开启虚拟机cpu虚拟化 解决&#xff1a; 1.资源不…

plotnine,一个非常实用的 Python 库!

大家好&#xff0c;今天为大家分享一个非常实用的 Python 库 - plotnine。 Github地址&#xff1a;https://github.com/has2k1/plotnine 在数据分析和可视化领域&#xff0c;Python 提供了许多强大的工具和库。其中&#xff0c;plotnine 是一个基于 Grammar of Graphics 理论的…

vmware workstation虚拟机报错”该虚拟机似乎正在使用中“

虚拟机报错&#xff1a; 解决方法&#xff1a; 进入到虚拟机的安装目录里&#xff0c;将lck结尾的文件删掉即可 重新点击虚拟机恢复正常

Nacos2.3.1集群部署

Nacos集群部署 1、下载安装包 https://github.com/alibaba/nacos/releases/download/2.3.1/nacos-server-2.3.1.tar.gz2、解压安装包 tar -xf nacos-server-2.3.1.tar.gz3、java环境配置 3.1、下载jdk17 https://download.oracle.com/java/17/archive/jdk-17.0.10_linux-x64…

读《Complementary Pseudo Multimodal Feature for Point Cloud Anomaly Detection》

摘要、引言 点云&#xff08;PCD&#xff09;异常检测逐渐成为一个很有前途的研究领域&#xff08;笑了&#xff09; 提出了互补伪多模态特征&#xff08;CPMF&#xff09;&#xff0c;该特征利用手工制作的PCD描述符在三维模态中包含局部几何信息&#xff08;2023还在搞手工制…

阿里云免费证书改为3个月,应对方法很简单

情商高点的说法是 Google 积极推进90天免费证书&#xff0c;各服务商积极响应。 情商低点的话&#xff0c;就是钱的问题。 现在基本各大服务商都在2024年停止签发1年期的免费SSL证书产品&#xff0c;有效期都缩短至3个月。 目前腾讯云倒还是一年期。 如果是一年期的话&#x…

dp求公共子序列

#include<iostream> using namespace std; int main(){string a1,a2;while(cin>>a1>>a2){int data[201][201]{};// 每次的最长记录for(int i1;i<a1.size();i){for(int j1;j<a2.size();j){if(a1[i-1] a2[j-1]){// 相等在2个字母未加进之前长度1data[i]…

图书管理系统|基于Springboot的图书管理系统设计与实现(源码+数据库+文档)​

图书管理系统目录 目录 基于Springboot的图书管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、个人中心 2、管理员管理 3、用户管理 4、图书出版社管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源…

【爬虫pyspider教程】1.pyspider入门与基本使用

前言 前文基本上把爬虫的流程实现一遍&#xff0c;将不同的功能定义成不同的方法&#xff0c;甚至抽象出模块的概念。如微信公众号爬虫&#xff0c;我们已经有了爬虫框架的雏形&#xff0c;如调度器、队列、请求对象等&#xff0c;但是它的架构和模块还是太简单&#xff0c;远…