對于內(nèi)容摘要,信件內(nèi)容進行全文檢索
基于SpringBoot 2.5.6+Postgresql+jpa+hibernate實現(xiàn)
依賴
<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>
業(yè)務(wù)邏輯
登記保存之后,處理完成業(yè)務(wù)邏輯,發(fā)送全文檢索事件
//附加類型對應(yīng)的附件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()));
//處理監(jiān)聽事件所需要的數(shù)據(jù)
Map<String,Object>eventData = Utils.buildMap("recordId", cmpRecord.getId(),"newRecord", true,"attCategoryToAttIds", attCategoryToAttIds);
//創(chuàng)建全文檢索事件
DomainEvent de = new DefaultDomainEvent(cmpRecord.getId() + "_Handle_CmpRecord_FullTextSearch", operateInfo, ExecutePoint.CURR_THREAD,
eventData, new Date(), "Handle_CmpRecord_FullTextSearch");
//發(fā)布事件
DomainEventPublisherFactory.getRegisteredPublisher().publishEvent(de);
處理業(yè)務(wù)發(fā)送全文檢索事件
@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);
//指定關(guān)聯(lián)關(guān)系
RelateProjValObj cmpRdProj=new RelateProjValObj(recordId,RelateProjConstants.PROJTYPE_CMP_RECORD);
//這是關(guān)聯(lián)那個業(yè)務(wù)
List<RelateProjValObj> mainProjs=Arrays.asList(cmpRdProj);
DomainEvent de=null;
//登記信息是無效的 則刪除已存在的和這個件相關(guān)的
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;
//在當(dāng)前線程中執(zhí)行,保證事務(wù)一致性
ExecutePoint executePoint=ExecutePoint.CURR_THREAD;
/***********************************************關(guān)鍵詞檢索-內(nèi)容摘要***********************************************/
//全文檢索的類型 區(qū)分內(nèi)容摘要 附件內(nèi)容
List<String> contentTypes=Arrays.asList(contentTypepPefix+"_contentAbstract");
String contentAbstract =cmdRecord.getBaseInfo().getContentAbstract();//內(nèi)容摘要
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);
/***********************************************關(guān)鍵詞檢索-信件內(nèi)容***********************************************/
contentTypes=Arrays.asList(contentTypepPefix+"_content");
String content =cmdRecord.getBaseInfo().getContent();//信件內(nèi)容
de=new FullTextSearchOperateEvent(recordId+"_FullTextSearch_Update", executePoint, operator, operateTime,
mainProjs, contentTypes, content, null);
DomainEventPublisherFactory.getRegisteredPublisher().publishEvent(de);
/***********************************************關(guān)鍵詞檢索-附件(原信等)***********************************************/
//如果附件也需要檢索 設(shè)置attIds參數(shù)
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();//公文相關(guān)附件
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);
}
}
統(tǒng)一處理全文檢索事件
@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都不存在 會刪除對應(yīng)關(guān)鍵詞檢索
"relProjs":List<RelateProjValObj> 可選 指定的需要添加的關(guān)系
"removeOtherRelProjs":false 可選 是否清除 指定relProjs以外的關(guān)聯(lián)記錄
*/
this.fullTextSearchUpdate(de);
}else if(FullTextSearchOperateEvent.EVENTTYPE_REMOVE.equals(de.getEventType())) {
/**
"mainProjs":List<RelateProjValObj> 必選
*/
this.fullTextSearchRemoveByProjs(de);
}
}
//關(guān)鍵詞檢索增加
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("數(shù)據(jù)指定錯誤");
}
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("數(shù)據(jù)指定錯誤");
}
//這是從附件中獲取文本數(shù)據(jù)
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();
}
}
//從數(shù)據(jù)庫中獲取已經(jīng)存的
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)) {
//如果內(nèi)容未空 則刪除
this.fullTextSearchRepository.removeFullTextSearch(oldFullText);
return;
}
//如果存在內(nèi)容,則更新
this.fullTextSearchRepository
.updateFullTextSearchContent(fullText.getId(), content, date);
}else {
if(StringUtils.isBlank(content)) {
return;
}
try {//否則 創(chuàng)建全文檢索記錄
this.fullTextSearchRepository.createFullTextSearch(fullText);
} catch (Exception e) {
e.printStackTrace();
return;
}
}
}
}
//關(guān)鍵詞檢索刪除 根據(jù)主相關(guān)件
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("數(shù)據(jù)指定錯誤");
}
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;//來源相關(guān)件
@Lob
@Type(type="org.hibernate.type.TextType")
private String content;//檢索內(nèi)容
@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();
}
}
}
存儲數(shù)據(jù)格式
查詢
sql大致就是這樣的邏輯文章來源:http://www.zghlxwxcb.cn/news/detail-838720.html
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.zghlxwxcb.cn/news/detail-838720.html
到了這里,關(guān)于基于數(shù)據(jù)庫的全文檢索實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!