forked from Gitlink/microservices
feat(全局搜索): 实现超算专区全局搜索功能
- 实现MySQL全文检索:基于MATCH...AGAINST NATURAL LANGUAGE MODE 搜索范围覆盖文档标题(name)和摘要(summary)双字段 结果按全文相关度降序+更新时间降序双重排序 仅检索已审核通过且非目录类型的文档 - 实现项目全文检索:基于MATCH...AGAINST搜索zone_project.name字段 仅返回已审核项目(audit_status='1'),按相关度+更新时间排序 - 实现资源全文检索:搜索资源名称(name)+关键词(keywords)复合字段 创新性支持附件文件名全文匹配(通过EXISTS子查询关联sys_file_info表) 资源结果附带匹配附件信息(文件ID、文件名、文件大小),存储于extendData 通过RemoteFileService远程调用获取附件详情,支持大小写不敏感匹配 - 引入Redis缓存策略:第一页搜索结果缓存5分钟
This commit is contained in:
parent
1c6563f915
commit
aff24c83d2
|
|
@ -126,6 +126,12 @@
|
|||
<version>3.6.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microservices</groupId>
|
||||
<artifactId>microservices-common-search</artifactId>
|
||||
<version>3.6.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
|||
|
|
@ -9,8 +9,12 @@ import com.microservices.common.core.web.controller.BaseController;
|
|||
import com.microservices.common.core.web.domain.AjaxResult;
|
||||
import com.microservices.common.core.web.domain.GenericsAjaxResult;
|
||||
import com.microservices.common.core.web.page.GenericsTableDataInfo;
|
||||
import com.microservices.common.core.web.page.TableDataInfo;
|
||||
import com.microservices.common.log.annotation.Log;
|
||||
import com.microservices.common.log.enums.BusinessType;
|
||||
import com.microservices.cms.search.dto.DocSearchRequest;
|
||||
import com.microservices.cms.search.dto.DocSearchResponse;
|
||||
import com.microservices.cms.search.service.IGlobalSearchService;
|
||||
import io.swagger.annotations.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
|
@ -35,6 +39,44 @@ public class OpenController extends BaseController {
|
|||
@Autowired
|
||||
private ICmsWechatService cmsWechatService;
|
||||
|
||||
@Autowired
|
||||
private IGlobalSearchService globalSearchService;
|
||||
|
||||
/**
|
||||
* 文章搜索接口
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param zoneId 专区ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 搜索结果
|
||||
*/
|
||||
@ApiOperation("文章搜索")
|
||||
@GetMapping("/search")
|
||||
public TableDataInfo docSearchGet(
|
||||
@ApiParam("搜索关键词") @RequestParam String keyword,
|
||||
@ApiParam("专区ID") @RequestParam Long zoneId,
|
||||
@ApiParam("页码") @RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@ApiParam("每页数量") @RequestParam(defaultValue = "20") Integer pageSize) {
|
||||
|
||||
DocSearchRequest request = new DocSearchRequest();
|
||||
request.setKeyword(keyword);
|
||||
request.setZoneId(zoneId);
|
||||
request.setPageNum(pageNum);
|
||||
request.setPageSize(pageSize);
|
||||
|
||||
logger.info("文章搜索请求(GET): keyword={}", keyword);
|
||||
|
||||
DocSearchResponse response = globalSearchService.docSearch(request);
|
||||
|
||||
TableDataInfo dataInfo = new TableDataInfo();
|
||||
dataInfo.setCode(200);
|
||||
dataInfo.setMsg("查询成功");
|
||||
dataInfo.setRows(response.getResults());
|
||||
dataInfo.setTotal(response.getTotal());
|
||||
return dataInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询栏目列表
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -192,4 +192,25 @@ public interface CmsDocMapper {
|
|||
List<CmsDoc> selectCmsDocAndDirListByDept(Long deptId);
|
||||
|
||||
void updateCmsDocShaByPathAndDeptId(@Param("sha") String sha, @Param("filePath") String filePath, @Param("deptId") Long deptId);
|
||||
|
||||
/**
|
||||
* 全局搜索 - 全文索引查询
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param deptId 组织ID(可选)
|
||||
* @param offset 偏移量
|
||||
* @param pageSize 每页数量
|
||||
* @return 搜索结果列表
|
||||
*/
|
||||
List<CmsDoc> globalSearch(@Param("keyword") String keyword, @Param("deptId") Long deptId,
|
||||
@Param("offset") int offset, @Param("pageSize") int pageSize);
|
||||
|
||||
/**
|
||||
* 全局搜索结果计数
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param deptId 组织ID(可选)
|
||||
* @return 结果数量
|
||||
*/
|
||||
Long globalSearchCount(@Param("keyword") String keyword, @Param("deptId") Long deptId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.microservices.cms.project.mapper;
|
|||
|
||||
import com.microservices.cms.project.domain.CmsProject;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -64,4 +65,25 @@ public interface CmsProjectMapper {
|
|||
CmsProject selectCmsProjectByDeptId(Long deptId);
|
||||
|
||||
CmsProject selectCmsProjectByIdentifier(String identifier);
|
||||
|
||||
/**
|
||||
* 全局搜索 - 全文索引查询
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param deptId 组织ID(可选)
|
||||
* @param offset 偏移量
|
||||
* @param pageSize 每页数量
|
||||
* @return 搜索结果列表
|
||||
*/
|
||||
List<CmsProject> globalSearch(@Param("keyword") String keyword, @Param("deptId") Long deptId,
|
||||
@Param("offset") int offset, @Param("pageSize") int pageSize);
|
||||
|
||||
/**
|
||||
* 全局搜索结果计数
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param deptId 组织ID(可选)
|
||||
* @return 结果数量
|
||||
*/
|
||||
Long globalSearchCount(@Param("keyword") String keyword, @Param("deptId") Long deptId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package com.microservices.cms.search.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 文章搜索请求对象
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("文章搜索请求对象")
|
||||
public class DocSearchRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 搜索关键词
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索关键词", required = true)
|
||||
private String keyword;
|
||||
|
||||
/**
|
||||
* 专区ID
|
||||
*/
|
||||
@ApiModelProperty(value = "专区ID")
|
||||
private Long zoneId;
|
||||
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
@ApiModelProperty(value = "页码")
|
||||
private Integer pageNum = 1;
|
||||
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
@ApiModelProperty(value = "每页数量")
|
||||
private Integer pageSize = 20;
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.microservices.cms.search.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局搜索响应对象
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("文章搜索响应对象")
|
||||
public class DocSearchResponse implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 总结果数
|
||||
*/
|
||||
@ApiModelProperty(value = "总结果数")
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 搜索结果列表
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索结果列表")
|
||||
private List<DocSearchResult> results;
|
||||
|
||||
/**
|
||||
* 各类型结果数量统计
|
||||
*/
|
||||
@ApiModelProperty(value = "各类型结果数量统计")
|
||||
private Map<String, Long> typeCounts;
|
||||
|
||||
/**
|
||||
* 搜索耗时(毫秒)
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索耗时(毫秒)")
|
||||
private Long searchTime;
|
||||
|
||||
/**
|
||||
* 搜索关键词
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索关键词")
|
||||
private String keyword;
|
||||
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
@ApiModelProperty(value = "当前页码")
|
||||
private Integer pageNum;
|
||||
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
@ApiModelProperty(value = "每页数量")
|
||||
private Integer pageSize;
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.microservices.cms.search.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文章搜索结果对象
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("文章搜索结果对象")
|
||||
public class DocSearchResult implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@ApiModelProperty(value = "ID")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
@ApiModelProperty(value = "标题")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 内容摘要
|
||||
*/
|
||||
@ApiModelProperty(value = "内容摘要")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 高亮片段
|
||||
*/
|
||||
@ApiModelProperty(value = "高亮片段")
|
||||
private String highlight;
|
||||
|
||||
/**
|
||||
* 组织ID
|
||||
*/
|
||||
@ApiModelProperty(value = "组织ID")
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@ApiModelProperty(value = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 相关度评分
|
||||
*/
|
||||
@ApiModelProperty(value = "相关度评分")
|
||||
private Double score;
|
||||
|
||||
/**
|
||||
* 扩展数据(JSON格式)
|
||||
*/
|
||||
@ApiModelProperty(value = "扩展数据")
|
||||
private String extendData;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.microservices.cms.search.service;
|
||||
|
||||
import com.microservices.cms.search.dto.DocSearchRequest;
|
||||
import com.microservices.cms.search.dto.DocSearchResponse;
|
||||
|
||||
/**
|
||||
* 全局搜索服务接口
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
public interface IGlobalSearchService {
|
||||
|
||||
/**
|
||||
* 全局搜索
|
||||
*
|
||||
* @param request 搜索请求
|
||||
* @return 搜索响应
|
||||
*/
|
||||
DocSearchResponse docSearch(DocSearchRequest request);
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
package com.microservices.cms.search.service.impl;
|
||||
|
||||
import com.microservices.cms.doc.domain.CmsDoc;
|
||||
import com.microservices.cms.doc.mapper.CmsDocMapper;
|
||||
import com.microservices.cms.doc.service.ICmsDocService;
|
||||
import com.microservices.cms.search.service.IGlobalSearchService;
|
||||
import com.microservices.common.core.utils.StringUtils;
|
||||
import com.microservices.common.redis.service.RedisService;
|
||||
import com.microservices.cms.search.dto.DocSearchRequest;
|
||||
import com.microservices.cms.search.dto.DocSearchResponse;
|
||||
import com.microservices.cms.search.dto.DocSearchResult;
|
||||
import com.microservices.common.security.utils.SecurityUtils;
|
||||
import com.microservices.system.api.model.LoginUser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 全局搜索服务实现
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Service
|
||||
public class GlobalSearchServiceImpl implements IGlobalSearchService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalSearchServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private CmsDocMapper cmsDocMapper;
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
@Autowired
|
||||
private ICmsDocService cmsDocService;
|
||||
|
||||
private static final String SEARCH_CACHE_PREFIX = "search:cache:";
|
||||
private static final Long CACHE_EXPIRE_SECONDS = 300L; // 5分钟
|
||||
|
||||
@Override
|
||||
public DocSearchResponse docSearch(DocSearchRequest request) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(request.getKeyword())) {
|
||||
return buildEmptyResponse(request);
|
||||
}
|
||||
|
||||
// 1. 尝试从缓存获取(仅第一页)
|
||||
if (request.getPageNum() == 1) {
|
||||
String cacheKey = buildCacheKey(request);
|
||||
|
||||
DocSearchResponse cached = redisService.getCacheObject(SEARCH_CACHE_PREFIX + cacheKey);
|
||||
if (cached != null) {
|
||||
cached.setSearchTime(System.currentTimeMillis() - startTime);
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 并行搜索各类型
|
||||
List<DocSearchResult> allResults = new ArrayList<>();
|
||||
Map<String, Long> typeCounts = new HashMap<>();
|
||||
|
||||
// 获取当前用户信息
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
|
||||
// 搜索CMS文章
|
||||
List<DocSearchResult> docResults = searchDocs(request);
|
||||
allResults.addAll(docResults);
|
||||
typeCounts.put("doc", (long) docResults.size());
|
||||
|
||||
// 3. 按相关度评分排序
|
||||
allResults.sort((a, b) -> {
|
||||
if (b.getScore() != null && a.getScore() != null) {
|
||||
return b.getScore().compareTo(a.getScore());
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
// 4. 分页
|
||||
int total = allResults.size();
|
||||
int start = (request.getPageNum() - 1) * request.getPageSize();
|
||||
int end = Math.min(start + request.getPageSize(), total);
|
||||
List<DocSearchResult> pagedResults = start < total ?
|
||||
allResults.subList(start, end) : new ArrayList<>();
|
||||
|
||||
// 5. 构建响应
|
||||
DocSearchResponse response = new DocSearchResponse();
|
||||
response.setTotal((long) total);
|
||||
response.setResults(pagedResults);
|
||||
response.setTypeCounts(typeCounts);
|
||||
response.setKeyword(request.getKeyword());
|
||||
response.setPageNum(request.getPageNum());
|
||||
response.setPageSize(request.getPageSize());
|
||||
response.setSearchTime(System.currentTimeMillis() - startTime);
|
||||
|
||||
// 6. 缓存结果(仅第一页)
|
||||
if (request.getPageNum() == 1) {
|
||||
String cacheKey = buildCacheKey(request);
|
||||
redisService.setCacheObject(SEARCH_CACHE_PREFIX + cacheKey, response, CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// 7. 记录搜索日志
|
||||
recordSearchLog(request, response, loginUser);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录搜索日志
|
||||
*/
|
||||
private void recordSearchLog(DocSearchRequest request, DocSearchResponse response, LoginUser loginUser) {
|
||||
Long userId = loginUser != null ? loginUser.getUserId() : null;
|
||||
String searchTypes = "文章";
|
||||
Long deptId = cmsDocService.getDeptIdByZoneId(request.getZoneId());
|
||||
logger.info("全局搜索 - 关键词: {}, 用户ID: {}, 耗时: {}ms, 结果数: {}, 类型: {}, 专区Id: {}, 组织ID: {}",
|
||||
request.getKeyword(),
|
||||
userId,
|
||||
response.getSearchTime(),
|
||||
response.getTotal(),
|
||||
searchTypes,
|
||||
request.getZoneId(),
|
||||
deptId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索CMS文章
|
||||
*/
|
||||
private List<DocSearchResult> searchDocs(DocSearchRequest request) {
|
||||
List<DocSearchResult> results = new ArrayList<>();
|
||||
try {
|
||||
// 使用部门ID参数
|
||||
Long deptId = cmsDocService.getDeptIdByZoneId(request.getZoneId());
|
||||
|
||||
List<CmsDoc> docs = cmsDocMapper.globalSearch(
|
||||
request.getKeyword(),
|
||||
deptId,
|
||||
(request.getPageNum() - 1) * request.getPageSize(),
|
||||
request.getPageSize()
|
||||
);
|
||||
|
||||
for (CmsDoc doc : docs) {
|
||||
DocSearchResult result = new DocSearchResult();
|
||||
result.setId(doc.getId());
|
||||
result.setTitle(doc.getName());
|
||||
result.setContent(doc.getSummary());
|
||||
result.setDeptId(doc.getDeptId());
|
||||
result.setCreateTime(doc.getCreateTime());
|
||||
result.setScore(1.0); // MySQL全文索引默认相关度
|
||||
results.add(result);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 记录错误但继续处理其他类型
|
||||
System.err.println("搜索CMS文章失败: " + e.getMessage());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建缓存键
|
||||
*/
|
||||
private String buildCacheKey(DocSearchRequest request) {
|
||||
Long deptId = cmsDocService.getDeptIdByZoneId(request.getZoneId());
|
||||
StringBuilder key = new StringBuilder(request.getKeyword());
|
||||
key.append(":").append(String.join(",", "Doc"));
|
||||
if (deptId != null) {
|
||||
key.append(":dept=").append(deptId);
|
||||
}
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建空响应
|
||||
*/
|
||||
private DocSearchResponse buildEmptyResponse(DocSearchRequest request) {
|
||||
DocSearchResponse response = new DocSearchResponse();
|
||||
response.setTotal(0L);
|
||||
response.setResults(new ArrayList<>());
|
||||
response.setTypeCounts(new HashMap<>());
|
||||
response.setKeyword(request.getKeyword());
|
||||
response.setPageNum(request.getPageNum());
|
||||
response.setPageSize(request.getPageSize());
|
||||
response.setSearchTime(0L);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -494,4 +494,32 @@
|
|||
#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<!-- 全局搜索 - 全文索引查询 -->
|
||||
<select id="globalSearch" resultMap="CmsDocResult">
|
||||
<include refid="selectCmsDocAliasVo"/>
|
||||
WHERE
|
||||
cd.is_dir = 0
|
||||
AND cd.audit_status = '1'
|
||||
AND MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
<if test="deptId != null">
|
||||
AND cd.dept_id = #{deptId}
|
||||
</if>
|
||||
ORDER BY
|
||||
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE) DESC,
|
||||
cd.update_time DESC
|
||||
LIMIT #{offset}, #{pageSize}
|
||||
</select>
|
||||
|
||||
<select id="globalSearchCount" resultType="java.lang.Long">
|
||||
SELECT COUNT(*)
|
||||
FROM cms_doc cd
|
||||
WHERE
|
||||
cd.is_dir = 0
|
||||
AND cd.audit_status = '1'
|
||||
AND MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
<if test="deptId != null">
|
||||
AND cd.dept_id = #{deptId}
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -159,4 +159,38 @@
|
|||
item="id" collection="array" open="(" separator="," close=")">#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<!-- 全局搜索 - 全文索引查询 -->
|
||||
<select id="globalSearch" resultMap="CmsProjectResult">
|
||||
SELECT id,
|
||||
name,
|
||||
description,
|
||||
user_id,
|
||||
dept_id,
|
||||
identifier,
|
||||
username,
|
||||
create_by,
|
||||
create_time,
|
||||
update_time
|
||||
FROM cms_project cp
|
||||
WHERE
|
||||
MATCH(cp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
<if test="deptId != null">
|
||||
AND cp.dept_id = #{deptId}
|
||||
</if>
|
||||
ORDER BY
|
||||
MATCH(cp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE) DESC,
|
||||
cp.update_time DESC
|
||||
LIMIT #{offset}, #{pageSize}
|
||||
</select>
|
||||
|
||||
<select id="globalSearchCount" resultType="java.lang.Long">
|
||||
SELECT COUNT(*)
|
||||
FROM cms_project cp
|
||||
WHERE
|
||||
MATCH(cp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
<if test="deptId != null">
|
||||
AND cp.dept_id = #{deptId}
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -133,6 +133,12 @@
|
|||
<version>3.6.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microservices</groupId>
|
||||
<artifactId>microservices-common-search</artifactId>
|
||||
<version>3.6.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.microservices.common.core.web.controller.BaseController;
|
|||
import com.microservices.common.core.web.domain.AjaxResult;
|
||||
import com.microservices.common.core.web.domain.GenericsAjaxResult;
|
||||
import com.microservices.common.core.web.page.GenericsTableDataInfo;
|
||||
import com.microservices.common.core.web.page.TableDataInfo;
|
||||
import com.microservices.common.log.annotation.Log;
|
||||
import com.microservices.common.log.enums.BusinessType;
|
||||
import com.microservices.system.api.domain.SysUser;
|
||||
|
|
@ -37,6 +38,10 @@ import com.microservices.zone.resource.domain.vo.ZoneResourceOpenSearchVo;
|
|||
import com.microservices.zone.resource.service.IZoneResourceDomainService;
|
||||
import com.microservices.zone.resource.service.IZoneResourceService;
|
||||
import com.microservices.zone.resource.service.IZoneResourceTypeService;
|
||||
import com.microservices.zone.search.dto.GlobalSearchRequest;
|
||||
import com.microservices.zone.search.dto.GlobalSearchResponse;
|
||||
import com.microservices.zone.search.dto.GlobalSearchResult;
|
||||
import com.microservices.zone.search.service.IGlobalSearchService;
|
||||
import com.microservices.zone.specialProject.domain.ZoneSpecialProject;
|
||||
import com.microservices.zone.specialProject.domain.ZoneSpecialProjectRelevancy;
|
||||
import com.microservices.zone.specialProject.domain.ZoneSpecialProjectReport;
|
||||
|
|
@ -411,4 +416,43 @@ public class OpenController extends BaseController {
|
|||
List<ZoneSpecialProjectReport> list = zoneSpecialProjectReportService.selectZoneSpecialProjectReportList(zoneSpecialProjectReportSearchVo.toZoneSpecialProjectReport());
|
||||
return getGenericsDataTable(list);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private IGlobalSearchService globalSearchService;
|
||||
|
||||
/**
|
||||
* 全局搜索接口(GET方法)
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param types 搜索类型(逗号分隔)
|
||||
* @param zoneId 专区ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 搜索结果
|
||||
*/
|
||||
@ApiOperation("全局搜索(GET)")
|
||||
@GetMapping("/global")
|
||||
public GenericsTableDataInfo<GlobalSearchResult> globalSearchGet(
|
||||
@ApiParam("搜索关键词") @RequestParam String keyword,
|
||||
@ApiParam("搜索类型") @RequestParam(required = false) String types,
|
||||
@ApiParam("专区ID") @RequestParam Long zoneId,
|
||||
@ApiParam("页码") @RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@ApiParam("每页数量") @RequestParam(defaultValue = "20") Integer pageSize) {
|
||||
|
||||
GlobalSearchRequest request = new GlobalSearchRequest();
|
||||
request.setKeyword(keyword);
|
||||
request.setPageNum(pageNum);
|
||||
request.setPageSize(pageSize);
|
||||
request.setZoneId(zoneId);
|
||||
|
||||
if (types != null && !types.isEmpty()) {
|
||||
request.setTypes(java.util.Arrays.asList(types.split(",")));
|
||||
}
|
||||
|
||||
logger.info("全局搜索请求(GET): keyword={}, types={}", keyword, types);
|
||||
|
||||
GlobalSearchResponse response = globalSearchService.globalSearch(request);
|
||||
|
||||
return new GenericsTableDataInfo<>(response.getResults(),response.getTotal());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,4 +155,28 @@ public interface ZoneProjectMapper {
|
|||
* @return
|
||||
*/
|
||||
int deleteZoneProjectByZoneId(Long zoneId);
|
||||
|
||||
/**
|
||||
* 全局搜索 - 全文索引查询
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param zoneId 专区ID(可选)
|
||||
* @param offset 偏移量
|
||||
* @param pageSize 每页数量
|
||||
* @return 搜索结果列表
|
||||
*/
|
||||
List<ZoneProject> globalSearch(@Param("keyword") String keyword, @Param("zoneId") Long zoneId,
|
||||
@Param("offset") int offset,
|
||||
@Param("pageSize") int pageSize);
|
||||
|
||||
/**
|
||||
* 全局搜索结果计数
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param zoneId 专区ID(可选)
|
||||
* @param deptId 组织ID(可选)
|
||||
* @return 结果数量
|
||||
*/
|
||||
Long globalSearchCount(@Param("keyword") String keyword, @Param("zoneId") Long zoneId,
|
||||
@Param("deptId") Long deptId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,4 +95,27 @@ public interface ZoneResourceMapper {
|
|||
* @param zoneId 专区Id
|
||||
*/
|
||||
void deleteZoneResourceByZoneId(Long zoneId);
|
||||
|
||||
/**
|
||||
* 全局搜索 - 全文索引查询
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param zoneId 专区ID
|
||||
* @param offset 偏移量
|
||||
* @param pageSize 每页数量
|
||||
* @return 搜索结果列表
|
||||
*/
|
||||
List<ZoneResource> globalSearch(@Param("keyword") String keyword, @Param("zoneId") Long zoneId,
|
||||
@Param("offset") int offset,@Param("pageSize") int pageSize);
|
||||
|
||||
/**
|
||||
* 全局搜索结果计数
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param zoneId 专区ID(可选)
|
||||
* @param deptId 组织ID(可选)
|
||||
* @return 结果数量
|
||||
*/
|
||||
Long globalSearchCount(@Param("keyword") String keyword, @Param("zoneId") Long zoneId,
|
||||
@Param("deptId") Long deptId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
package com.microservices.zone.search.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 全局搜索请求对象
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("全局搜索请求对象")
|
||||
public class GlobalSearchRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 搜索关键词
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索关键词", required = true)
|
||||
private String keyword;
|
||||
|
||||
/**
|
||||
* 搜索类型列表(zone_project, resource, member)
|
||||
* 为空则搜索所有类型
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索类型列表")
|
||||
private List<String> types;
|
||||
|
||||
/**
|
||||
* 专区ID
|
||||
*/
|
||||
@ApiModelProperty(value = "专区ID")
|
||||
private Long zoneId;
|
||||
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
@ApiModelProperty(value = "页码")
|
||||
private Integer pageNum = 1;
|
||||
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
@ApiModelProperty(value = "每页数量")
|
||||
private Integer pageSize = 20;
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.microservices.zone.search.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局搜索响应对象
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("全局搜索响应对象")
|
||||
public class GlobalSearchResponse implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 总结果数
|
||||
*/
|
||||
@ApiModelProperty(value = "总结果数")
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 搜索结果列表
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索结果列表")
|
||||
private List<GlobalSearchResult> results;
|
||||
|
||||
/**
|
||||
* 各类型结果数量统计
|
||||
*/
|
||||
@ApiModelProperty(value = "各类型结果数量统计")
|
||||
private Map<String, Long> typeCounts;
|
||||
|
||||
/**
|
||||
* 搜索耗时(毫秒)
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索耗时(毫秒)")
|
||||
private Long searchTime;
|
||||
|
||||
/**
|
||||
* 搜索关键词
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索关键词")
|
||||
private String keyword;
|
||||
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
@ApiModelProperty(value = "当前页码")
|
||||
private Integer pageNum;
|
||||
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
@ApiModelProperty(value = "每页数量")
|
||||
private Integer pageSize;
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package com.microservices.zone.search.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 全局搜索结果对象
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("全局搜索结果对象")
|
||||
public class GlobalSearchResult implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 结果类型(zone_project, resource, member)
|
||||
*/
|
||||
@ApiModelProperty(value = "结果类型")
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@ApiModelProperty(value = "ID")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
@ApiModelProperty(value = "标题")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 内容摘要
|
||||
*/
|
||||
@ApiModelProperty(value = "内容摘要")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 高亮片段
|
||||
*/
|
||||
@ApiModelProperty(value = "高亮片段")
|
||||
private String highlight;
|
||||
|
||||
/**
|
||||
* 组织ID
|
||||
*/
|
||||
@ApiModelProperty(value = "组织ID")
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 专区ID
|
||||
*/
|
||||
@ApiModelProperty(value = "专区ID")
|
||||
private Long zoneId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@ApiModelProperty(value = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 相关度评分
|
||||
*/
|
||||
@ApiModelProperty(value = "相关度评分")
|
||||
private Double score;
|
||||
|
||||
/**
|
||||
* 扩展数据(JSON格式)
|
||||
*/
|
||||
@ApiModelProperty(value = "扩展数据")
|
||||
private String extendData;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.microservices.zone.search.service;
|
||||
|
||||
import com.microservices.zone.search.dto.GlobalSearchRequest;
|
||||
import com.microservices.zone.search.dto.GlobalSearchResponse;
|
||||
|
||||
/**
|
||||
* 全局搜索服务接口
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
public interface IGlobalSearchService {
|
||||
|
||||
/**
|
||||
* 全局搜索
|
||||
*
|
||||
* @param request 搜索请求
|
||||
* @return 搜索响应
|
||||
*/
|
||||
GlobalSearchResponse globalSearch(GlobalSearchRequest request);
|
||||
}
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
package com.microservices.zone.search.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.microservices.common.core.domain.R;
|
||||
import com.microservices.common.core.utils.StringUtils;
|
||||
import com.microservices.common.redis.service.RedisService;
|
||||
import com.microservices.common.security.utils.SecurityUtils;
|
||||
import com.microservices.system.api.RemoteFileService;
|
||||
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.mapper.ZoneProjectMapper;
|
||||
import com.microservices.zone.resource.domain.ZoneResource;
|
||||
import com.microservices.zone.resource.mapper.ZoneResourceMapper;
|
||||
import com.microservices.zone.search.dto.GlobalSearchRequest;
|
||||
import com.microservices.zone.search.dto.GlobalSearchResponse;
|
||||
import com.microservices.zone.search.dto.GlobalSearchResult;
|
||||
import com.microservices.zone.search.service.IGlobalSearchService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 全局搜索服务实现
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Service
|
||||
public class GlobalSearchServiceImpl implements IGlobalSearchService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalSearchServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private ZoneProjectMapper zoneProjectMapper;
|
||||
|
||||
@Autowired
|
||||
private ZoneResourceMapper zoneResourceMapper;
|
||||
|
||||
@Autowired
|
||||
private RemoteFileService remoteFileService;
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
private static final String SEARCH_CACHE_PREFIX = "search:cache:";
|
||||
private static final long CACHE_EXPIRE_SECONDS = 300; // 5分钟
|
||||
|
||||
@Override
|
||||
public GlobalSearchResponse globalSearch(GlobalSearchRequest request) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(request.getKeyword())) {
|
||||
return buildEmptyResponse(request);
|
||||
}
|
||||
|
||||
// 1. 尝试从缓存获取(仅第一页)
|
||||
if (request.getPageNum() == 1) {
|
||||
String cacheKey = buildCacheKey(request);
|
||||
GlobalSearchResponse cached = redisService.getCacheObject(SEARCH_CACHE_PREFIX + cacheKey);
|
||||
if (cached != null) {
|
||||
cached.setSearchTime(System.currentTimeMillis() - startTime);
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 并行搜索各类型
|
||||
List<GlobalSearchResult> allResults = new ArrayList<>();
|
||||
Map<String, Long> typeCounts = new HashMap<>();
|
||||
|
||||
// 获取当前用户信息
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
|
||||
// 确定要搜索的类型
|
||||
List<String> searchTypes = request.getTypes();
|
||||
if (searchTypes == null || searchTypes.isEmpty()) {
|
||||
searchTypes = Arrays.asList("zone_project", "resource", "member");
|
||||
}
|
||||
|
||||
// 搜索Zone项目
|
||||
if (searchTypes.contains("zone_project")) {
|
||||
List<GlobalSearchResult> zoneProjectResults = searchZoneProjects(request);
|
||||
allResults.addAll(zoneProjectResults);
|
||||
typeCounts.put("zone_project", (long) zoneProjectResults.size());
|
||||
}
|
||||
|
||||
// 搜索Zone资源
|
||||
if (searchTypes.contains("resource")) {
|
||||
List<GlobalSearchResult> resourceResults = searchResources(request);
|
||||
allResults.addAll(resourceResults);
|
||||
typeCounts.put("resource", (long) resourceResults.size());
|
||||
}
|
||||
|
||||
// 3. 按相关度评分排序
|
||||
allResults.sort((a, b) -> {
|
||||
if (b.getScore() != null && a.getScore() != null) {
|
||||
return b.getScore().compareTo(a.getScore());
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
// 4. 分页
|
||||
int total = allResults.size();
|
||||
int start = (request.getPageNum() - 1) * request.getPageSize();
|
||||
int end = Math.min(start + request.getPageSize(), total);
|
||||
List<GlobalSearchResult> pagedResults = start < total ?
|
||||
allResults.subList(start, end) : new ArrayList<>();
|
||||
|
||||
// 5. 构建响应
|
||||
GlobalSearchResponse response = new GlobalSearchResponse();
|
||||
response.setTotal((long) total);
|
||||
response.setResults(pagedResults);
|
||||
response.setTypeCounts(typeCounts);
|
||||
response.setKeyword(request.getKeyword());
|
||||
response.setPageNum(request.getPageNum());
|
||||
response.setPageSize(request.getPageSize());
|
||||
response.setSearchTime(System.currentTimeMillis() - startTime);
|
||||
|
||||
// 6. 缓存结果(仅第一页)
|
||||
if (request.getPageNum() == 1) {
|
||||
String cacheKey = buildCacheKey(request);
|
||||
redisService.setCacheObject(
|
||||
SEARCH_CACHE_PREFIX + cacheKey,
|
||||
response,
|
||||
CACHE_EXPIRE_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// 7. 异步记录搜索日志
|
||||
recordSearchLog(request, response, loginUser);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步记录搜索日志
|
||||
*/
|
||||
@Async
|
||||
public void recordSearchLog(GlobalSearchRequest request, GlobalSearchResponse response, LoginUser loginUser) {
|
||||
try {
|
||||
logger.info("全局搜索 - 关键词: {}, 用户ID: {}, 耗时: {}ms, 结果数: {}, 搜索类型: {}, 专区ID: {}",
|
||||
request.getKeyword(),
|
||||
loginUser != null ? loginUser.getUserId() : null,
|
||||
response.getSearchTime(),
|
||||
response.getTotal(),
|
||||
request.getTypes() != null ? String.join(",", request.getTypes()) : null,
|
||||
request.getZoneId()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
logger.error("记录搜索日志失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索Zone项目
|
||||
*/
|
||||
private List<GlobalSearchResult> searchZoneProjects(GlobalSearchRequest request) {
|
||||
List<GlobalSearchResult> results = new ArrayList<>();
|
||||
try {
|
||||
List<ZoneProject> projects = zoneProjectMapper.globalSearch(
|
||||
request.getKeyword(),
|
||||
request.getZoneId(),
|
||||
(request.getPageNum() - 1) * request.getPageSize(),
|
||||
request.getPageSize()
|
||||
);
|
||||
|
||||
for (ZoneProject 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());
|
||||
result.setScore(1.0);
|
||||
results.add(result);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("搜索Zone项目失败: " + e.getMessage());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索Zone资源
|
||||
*/
|
||||
private List<GlobalSearchResult> searchResources(GlobalSearchRequest request) {
|
||||
List<GlobalSearchResult> results = new ArrayList<>();
|
||||
try {
|
||||
List<ZoneResource> resources = zoneResourceMapper.globalSearch(
|
||||
request.getKeyword(),
|
||||
request.getZoneId(),
|
||||
(request.getPageNum() - 1) * request.getPageSize(),
|
||||
request.getPageSize()
|
||||
);
|
||||
|
||||
for (ZoneResource resource : resources) {
|
||||
GlobalSearchResult result = new GlobalSearchResult();
|
||||
result.setType("resource");
|
||||
result.setId(resource.getId());
|
||||
result.setTitle(resource.getName());
|
||||
result.setContent(resource.getSummary());
|
||||
result.setDeptId(resource.getDeptId());
|
||||
result.setZoneId(resource.getZoneId());
|
||||
result.setCreateTime(resource.getCreateTime());
|
||||
result.setScore(1.0);
|
||||
|
||||
// 查询并过滤匹配的附件
|
||||
if (StringUtils.isNotEmpty(resource.getFileIds())) {
|
||||
try {
|
||||
R<List<SysFileInfo>> fileResponse = remoteFileService.getFileList(resource.getFileIds());
|
||||
if (fileResponse != null && fileResponse.getCode() == 200 && fileResponse.getData() != null) {
|
||||
List<Map<String, Object>> matchedFiles = fileResponse.getData().stream()
|
||||
.filter(file -> StringUtils.isNotEmpty(file.getFileOriginName())
|
||||
&& file.getFileOriginName().toLowerCase().contains(request.getKeyword().toLowerCase()))
|
||||
.map(file -> {
|
||||
Map<String, Object> fileInfo = new HashMap<>();
|
||||
fileInfo.put("fileId", file.getFileId());
|
||||
fileInfo.put("fileName", file.getFileOriginName());
|
||||
fileInfo.put("fileSize", file.getFileSizeInfo());
|
||||
return fileInfo;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!matchedFiles.isEmpty()) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
Map<String, Object> extendData = new HashMap<>();
|
||||
extendData.put("matchedFiles", matchedFiles);
|
||||
result.setExtendData(objectMapper.writeValueAsString(extendData));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("查询附件信息失败: resourceId={}, error={}", resource.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
results.add(result);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("搜索Zone资源失败: " + e.getMessage());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建缓存键
|
||||
*/
|
||||
private String buildCacheKey(GlobalSearchRequest request) {
|
||||
StringBuilder key = new StringBuilder(request.getKeyword());
|
||||
if (request.getTypes() != null && !request.getTypes().isEmpty()) {
|
||||
key.append(":").append(String.join(",", request.getTypes()));
|
||||
}
|
||||
if (request.getZoneId() != null) {
|
||||
key.append(":zone=").append(request.getZoneId());
|
||||
}
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建空响应
|
||||
*/
|
||||
private GlobalSearchResponse buildEmptyResponse(GlobalSearchRequest request) {
|
||||
GlobalSearchResponse response = new GlobalSearchResponse();
|
||||
response.setTotal(0L);
|
||||
response.setResults(new ArrayList<>());
|
||||
response.setTypeCounts(new HashMap<>());
|
||||
response.setKeyword(request.getKeyword());
|
||||
response.setPageNum(request.getPageNum());
|
||||
response.setPageSize(request.getPageSize());
|
||||
response.setSearchTime(0L);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -276,4 +276,48 @@
|
|||
from zone_project
|
||||
where zone_id = #{zoneId}
|
||||
</delete>
|
||||
|
||||
<!-- 全局搜索 - 全文索引查询 -->
|
||||
<select id="globalSearch" resultMap="ZoneProjectResult">
|
||||
SELECT zp.id,
|
||||
zp.name,
|
||||
zp.sort,
|
||||
zp.full_name,
|
||||
zp.gitlink_project_id,
|
||||
zp.dept_id,
|
||||
zp.zone_id,
|
||||
zp.del_flag,
|
||||
zp.create_by,
|
||||
zp.create_time,
|
||||
zp.update_by,
|
||||
zp.update_time,
|
||||
zp.project_type_id,
|
||||
zp.is_homepage,
|
||||
zp.audit_status,
|
||||
zp.audit_time,
|
||||
zp.audit_by
|
||||
FROM zone_project zp
|
||||
WHERE
|
||||
MATCH(zp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
AND zp.zone_id = #{zoneId}
|
||||
AND zp.audit_status = '1'
|
||||
ORDER BY
|
||||
MATCH(zp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE) DESC,
|
||||
zp.update_time DESC
|
||||
LIMIT #{offset}, #{pageSize}
|
||||
</select>
|
||||
|
||||
<select id="globalSearchCount" resultType="java.lang.Long">
|
||||
SELECT COUNT(*)
|
||||
FROM zone_project zp
|
||||
WHERE
|
||||
MATCH(zp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
<if test="zoneId != null">
|
||||
AND zp.zone_id = #{zoneId}
|
||||
</if>
|
||||
<if test="deptId != null">
|
||||
AND zp.dept_id = #{deptId}
|
||||
</if>
|
||||
AND zp.audit_status = '1'
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -301,4 +301,70 @@
|
|||
from zone_resource
|
||||
where zone_id = #{zoneId}
|
||||
</delete>
|
||||
|
||||
<!-- 全局搜索 - 全文索引查询(含附件名称匹配) -->
|
||||
<select id="globalSearch" resultMap="ZoneResourceResult">
|
||||
SELECT zr.id,
|
||||
zr.name,
|
||||
zr.file_ids,
|
||||
zr.domain_id,
|
||||
zr.sort,
|
||||
zr.is_homepage,
|
||||
zr.zone_id,
|
||||
zr.dept_id,
|
||||
zr.del_flag,
|
||||
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
|
||||
FROM zone_resource zr
|
||||
WHERE
|
||||
zr.zone_id = #{zoneId}
|
||||
AND zr.audit_status = '1'
|
||||
AND (
|
||||
MATCH(zr.name, zr.keywords) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM sys_file_info sf
|
||||
WHERE FIND_IN_SET(sf.file_id, zr.file_ids)
|
||||
AND MATCH(sf.file_origin_name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
)
|
||||
)
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN MATCH(zr.name, zr.keywords) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE) > 0
|
||||
THEN MATCH(zr.name, zr.keywords) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
ELSE 0
|
||||
END DESC,
|
||||
zr.update_time DESC
|
||||
LIMIT #{offset}, #{pageSize}
|
||||
</select>
|
||||
|
||||
<select id="globalSearchCount" resultType="java.lang.Long">
|
||||
SELECT COUNT(*)
|
||||
FROM zone_resource zr
|
||||
WHERE
|
||||
zr.audit_status = '1'
|
||||
AND (
|
||||
MATCH(zr.name, zr.keywords) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM sys_file_info sf
|
||||
WHERE FIND_IN_SET(sf.file_id, zr.file_ids)
|
||||
AND MATCH(sf.file_origin_name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
)
|
||||
)
|
||||
<if test="zoneId != null">
|
||||
AND zr.zone_id = #{zoneId}
|
||||
</if>
|
||||
<if test="deptId != null">
|
||||
AND zr.dept_id = #{deptId}
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
Loading…
Reference in New Issue