feat(全局搜索): 实现超算专区全局搜索功能

1. 搜索专区项目时返回项目的语言、项目类别、最佳更新时间
2. 搜索专区资源时返回资源的附件数量、下载次数、发布时间
3. 搜索专区新闻时返回新闻的阅读数、发布时间
4. 搜索专区会员时返回会员的会员名称、会员分类名称、会员头像
This commit is contained in:
欧涛 2026-04-17 08:46:29 +08:00
parent a0384614d3
commit fded90d57c
9 changed files with 61 additions and 23 deletions

View File

@ -163,6 +163,7 @@ public class GlobalSearchServiceImpl implements IGlobalSearchService {
JSONObject extendData = new JSONObject();
extendData.put("visits", doc.getVisits());
extendData.put("publishTime", doc.getPublishTime());
result.setExtendData(extendData);
results.add(result);
}

View File

@ -19,4 +19,6 @@ public class ZoneMemberWithScore extends ZoneMember implements Serializable {
* 全文搜索相关度评分来自 MySQL MATCH...AGAINST
*/
private Double searchScore;
private String memberTypeName;
}

View File

@ -1,6 +1,9 @@
package com.microservices.zone.project.domain;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.zone.project.domain.ZoneProject;
import com.microservices.zone.project.domain.vo.ZoneProjectDataVo;
import com.microservices.zone.search.dto.GlobalSearchResult;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -19,4 +22,27 @@ public class ZoneProjectWithScore extends ZoneProject implements Serializable {
* 全文搜索相关度评分来自 MySQL MATCH...AGAINST
*/
private Double searchScore;
public GlobalSearchResult toGlobalSearchResult(ZoneProjectDataVo zoneProjectDataVo) {
GlobalSearchResult result = new GlobalSearchResult();
result.setType("zone_project");
result.setId(this.getId());
result.setTitle(this.getName());
result.setContent(this.getFullName());
result.setDeptId(this.getDeptId());
result.setZoneId(this.getZoneId());
result.setCreateTime(this.getCreateTime());
if (zoneProjectDataVo != null) {
result.setExtendData(zoneProjectDataVo.getProjectProperties());
}
// 使用数据库返回的实际搜索评分
Double score = this.getSearchScore();
if (score == null || score == 0.0) {
score = 0.1; // LIKE 匹配的最小评分
}
result.setScore(score);
return result;
}
}

View File

@ -2,6 +2,7 @@ package com.microservices.zone.project.service;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.web.page.GenericsTableDataInfo;
import com.microservices.zone.project.domain.ZoneProject;
import com.microservices.zone.project.domain.vo.*;
import java.util.List;
@ -147,4 +148,5 @@ public interface IZoneProjectService {
int auditZoneProject(ZoneProjectAuditVo zoneProjectAuditVo);
GenericsTableDataInfo<JSONObject> selectMyGitlinkProjectListByZone(Long zoneId, MyGitLinkProjectSearchVo myGitLinkProjectSearchVo);
List<ZoneProjectDataVo> toZoneProjectDataVoList(List<ZoneProject> zoneProjectList);
}

View File

@ -125,7 +125,8 @@ public class ZoneProjectServiceImpl implements IZoneProjectService {
return PageUtils.toListPage(zoneProjectList, this::toZoneProjectDataVoList);
}
private List<ZoneProjectDataVo> toZoneProjectDataVoList(List<ZoneProject> zoneProjectList) {
@Override
public List<ZoneProjectDataVo> toZoneProjectDataVoList(List<ZoneProject> zoneProjectList) {
List<ZoneProjectDataVo> zoneProjectDataVoList = new ArrayList<>();
// 原方案为遍历项目聚合再逐一从GitLink获取对应项目详情此方案延时过高调整为根据项目Id批量获取GitLink项目信息
List<String> gitLinkProjectIdList = zoneProjectList

View File

@ -19,4 +19,6 @@ public class ZoneResourceWithScore extends ZoneResource implements Serializable
* 全文搜索相关度评分来自 MySQL MATCH...AGAINST
*/
private Double searchScore;
private Long downloadCount;
}

View File

@ -11,7 +11,9 @@ import com.microservices.system.api.domain.SysFileInfo;
import com.microservices.system.api.model.LoginUser;
import com.microservices.zone.project.domain.ZoneProject;
import com.microservices.zone.project.domain.ZoneProjectWithScore;
import com.microservices.zone.project.domain.vo.ZoneProjectDataVo;
import com.microservices.zone.project.mapper.ZoneProjectMapper;
import com.microservices.zone.project.service.IZoneProjectService;
import com.microservices.zone.resource.domain.ZoneResource;
import com.microservices.zone.resource.domain.ZoneResourceWithScore;
import com.microservices.zone.resource.mapper.ZoneResourceMapper;
@ -57,6 +59,9 @@ public class GlobalSearchServiceImpl implements IGlobalSearchService {
@Autowired
private RedisService redisService;
@Autowired
private IZoneProjectService projectService;
private static final String SEARCH_CACHE_PREFIX = "search:cache:";
private static final long CACHE_EXPIRE_SECONDS = 300; // 5分钟
@ -185,23 +190,12 @@ public class GlobalSearchServiceImpl implements IGlobalSearchService {
(request.getPageNum() - 1) * request.getPageSize(),
request.getPageSize()
);
List<ZoneProject> zoneProjectList = projects.stream().map(x -> (ZoneProject) x).collect(Collectors.toList());
List<ZoneProjectDataVo> projectDataVoList = projectService.toZoneProjectDataVoList(zoneProjectList);
for (ZoneProjectWithScore project : projects) {
GlobalSearchResult result = new GlobalSearchResult();
result.setType("zone_project");
result.setId(project.getId());
result.setTitle(project.getName());
result.setContent(project.getFullName());
result.setDeptId(project.getDeptId());
result.setZoneId(project.getZoneId());
result.setCreateTime(project.getCreateTime());
// 使用数据库返回的实际搜索评分
Double score = project.getSearchScore();
if (score == null || score == 0.0) {
score = 0.1; // LIKE 匹配的最小评分
}
result.setScore(score);
ZoneProjectDataVo zoneProjectDataVo = projectDataVoList.stream().filter(x -> x.getId().equals(project.getId())).findFirst().orElse(null);
GlobalSearchResult result = project.toGlobalSearchResult(zoneProjectDataVo);
results.add(result);
}
} catch (Exception e) {
@ -243,6 +237,14 @@ public class GlobalSearchServiceImpl implements IGlobalSearchService {
// 查询并过滤匹配的附件
if (StringUtils.isNotEmpty(resource.getFileIds())) {
try {
JSONObject extendData = new JSONObject();
extendData.put("downloadCount", resource.getDownloadCount());
if (resource.getFileIds() != null) {
String[] fileIds = resource.getFileIds().split(",");
extendData.put("fileCount", fileIds.length);
}
R<List<SysFileInfo>> fileResponse = remoteFileService.getFileList(resource.getFileIds());
if (fileResponse != null && fileResponse.getCode() == 200 && fileResponse.getData() != null) {
List<JSONObject> matchedFiles = fileResponse.getData().stream()
@ -258,11 +260,12 @@ public class GlobalSearchServiceImpl implements IGlobalSearchService {
.collect(Collectors.toList());
if (!matchedFiles.isEmpty()) {
JSONObject extendData = new JSONObject();
extendData.put("matchedFiles", matchedFiles);
result.setExtendData(extendData);
}
}
result.setExtendData(extendData);
} catch (Exception e) {
logger.warn("查询附件信息失败: resourceId={}, error={}", resource.getId(), e.getMessage());
}
@ -326,12 +329,10 @@ public class GlobalSearchServiceImpl implements IGlobalSearchService {
if (StringUtils.isNotEmpty(member.getImageUrl())) {
extendData.put("imageUrl", member.getImageUrl());
}
if (member.getMemberLevelId() != null) {
extendData.put("memberLevelId", member.getMemberLevelId());
}
if (member.getSysUser() != null && StringUtils.isNotEmpty(member.getSysUser().getAvatar())) {
extendData.put("userAvatar", member.getSysUser().getAvatar());
if (StringUtils.isNotEmpty(member.getMemberTypeName())) {
extendData.put("memberTypeName", member.getMemberTypeName());
}
result.setExtendData(extendData);
} catch (Exception e) {
logger.warn("构建会员扩展数据失败: memberId={}, error={}", member.getId(), e.getMessage());

View File

@ -64,6 +64,7 @@
<resultMap type="com.microservices.zone.member.domain.ZoneMemberWithScore"
id="ZoneMemberWithScoreResult" extends="ZoneMemberResult">
<result property="searchScore" column="search_score"/>
<result property="memberTypeName" column="typeName"/>
</resultMap>
<sql id="selectZoneMemberAliasVo">

View File

@ -41,6 +41,7 @@
<resultMap type="com.microservices.zone.resource.domain.ZoneResourceWithScore"
id="ZoneResourceWithScoreResult" extends="ZoneResourceResult">
<result property="searchScore" column="search_score"/>
<result property="downloadCount" column="download_count"/>
</resultMap>
<sql id="selectZoneResourceVo">
@ -361,6 +362,7 @@
zr.create_by, zr.create_time, zr.update_by, zr.update_time,
zr.editor_type, zr.audit_status, zr.audit_time, zr.audit_by,
zr.summary, zr.remark, zr.keywords, zr.extend_data,
(select sum(download_count) from sys_file_info sf where FIND_IN_SET(sf.file_id,zr.file_ids)) as download_count,
MATCH(zr.name, zr.keywords) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE) AS search_score
FROM zone_resource zr
WHERE zr.zone_id = #{zoneId}