feat(search): update search documents and query services

This commit is contained in:
vsxd 2026-03-12 17:47:18 +08:00
parent d44f9415fe
commit db0d86ea08
4 changed files with 61 additions and 40 deletions

View file

@ -3,7 +3,7 @@ package com.iflytek.skillhub.search;
import java.util.Set;
public record SearchVisibilityScope(
Long userId,
String userId,
Set<Long> memberNamespaceIds,
Set<Long> adminNamespaceIds
) {

View file

@ -4,7 +4,7 @@ public record SkillSearchDocument(
Long skillId,
Long namespaceId,
String namespaceSlug,
Long ownerId,
String ownerId,
String title,
String summary,
String keywords,

View file

@ -8,6 +8,7 @@ import jakarta.persistence.Query;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
@Service
public class PostgresFullTextQueryService implements SearchQueryService {
@ -20,14 +21,21 @@ public class PostgresFullTextQueryService implements SearchQueryService {
@Override
public SearchResult search(SearchQuery query) {
Set<Long> memberNamespaceIds = query.visibilityScope().memberNamespaceIds().isEmpty()
? Set.of(-1L)
: query.visibilityScope().memberNamespaceIds();
Set<Long> adminNamespaceIds = query.visibilityScope().adminNamespaceIds().isEmpty()
? Set.of(-1L)
: query.visibilityScope().adminNamespaceIds();
StringBuilder sql = new StringBuilder();
sql.append("SELECT skill_id FROM skill_search_document WHERE 1=1 ");
// Visibility filtering
sql.append("AND (visibility = 'PUBLIC' ");
if (query.visibilityScope().userId() != null) {
sql.append("OR (visibility = 'NAMESPACE' AND namespace_id IN :memberNamespaceIds) ");
sql.append("OR (visibility = 'PRIVATE' AND namespace_id IN :adminNamespaceIds) ");
sql.append("OR (visibility = 'NAMESPACE_ONLY' AND namespace_id IN :memberNamespaceIds) ");
sql.append("OR (visibility = 'PRIVATE' AND (namespace_id IN :adminNamespaceIds OR owner_id = :userId)) ");
}
sql.append(") ");
@ -41,16 +49,18 @@ public class PostgresFullTextQueryService implements SearchQueryService {
// Full-text search
if (query.keyword() != null && !query.keyword().isBlank()) {
sql.append("AND to_tsvector('english', search_text) @@ plainto_tsquery('english', :keyword) ");
sql.append("AND search_vector @@ plainto_tsquery('simple', :keyword) ");
}
// Sorting
if ("downloads".equals(query.sortBy())) {
sql.append("ORDER BY (SELECT download_count FROM skill WHERE id = skill_id) DESC ");
} else if ("rating".equals(query.sortBy())) {
sql.append("ORDER BY (SELECT rating_avg FROM skill WHERE id = skill_id) DESC ");
} else if ("newest".equals(query.sortBy())) {
sql.append("ORDER BY (SELECT created_at FROM skill WHERE id = skill_id) DESC ");
sql.append("ORDER BY (SELECT updated_at FROM skill WHERE id = skill_id) DESC ");
} else if ("relevance".equals(query.sortBy()) && query.keyword() != null && !query.keyword().isBlank()) {
sql.append("ORDER BY ts_rank(to_tsvector('english', search_text), plainto_tsquery('english', :keyword)) DESC ");
sql.append("ORDER BY ts_rank(search_vector, plainto_tsquery('simple', :keyword)) DESC ");
} else {
sql.append("ORDER BY updated_at DESC ");
}
@ -61,8 +71,9 @@ public class PostgresFullTextQueryService implements SearchQueryService {
Query nativeQuery = entityManager.createNativeQuery(sql.toString());
if (query.visibilityScope().userId() != null) {
nativeQuery.setParameter("memberNamespaceIds", query.visibilityScope().memberNamespaceIds());
nativeQuery.setParameter("adminNamespaceIds", query.visibilityScope().adminNamespaceIds());
nativeQuery.setParameter("memberNamespaceIds", memberNamespaceIds);
nativeQuery.setParameter("adminNamespaceIds", adminNamespaceIds);
nativeQuery.setParameter("userId", query.visibilityScope().userId());
}
if (query.namespaceId() != null) {
@ -83,14 +94,21 @@ public class PostgresFullTextQueryService implements SearchQueryService {
// Count total
String countSql = sql.toString().replaceFirst("SELECT skill_id", "SELECT COUNT(*)");
countSql = countSql.substring(0, countSql.indexOf("ORDER BY"));
countSql = countSql.substring(0, countSql.indexOf("LIMIT"));
int orderByIndex = countSql.indexOf("ORDER BY");
if (orderByIndex >= 0) {
countSql = countSql.substring(0, orderByIndex);
}
int limitIndex = countSql.indexOf("LIMIT");
if (limitIndex >= 0) {
countSql = countSql.substring(0, limitIndex);
}
Query countQuery = entityManager.createNativeQuery(countSql);
if (query.visibilityScope().userId() != null) {
countQuery.setParameter("memberNamespaceIds", query.visibilityScope().memberNamespaceIds());
countQuery.setParameter("adminNamespaceIds", query.visibilityScope().adminNamespaceIds());
countQuery.setParameter("memberNamespaceIds", memberNamespaceIds);
countQuery.setParameter("adminNamespaceIds", adminNamespaceIds);
countQuery.setParameter("userId", query.visibilityScope().userId());
}
if (query.namespaceId() != null) {

View file

@ -31,9 +31,12 @@ public class PostgresSearchRebuildService implements SearchRebuildService {
@Override
public void rebuildAll() {
// This would need a findAll method in SkillRepository
// For now, we'll leave it as a placeholder
throw new UnsupportedOperationException("rebuildAll not yet implemented");
List<SkillSearchDocument> documents = skillRepository.findAll().stream()
.filter(skill -> skill.getStatus() == SkillStatus.ACTIVE)
.map(this::toDocument)
.flatMap(Optional::stream)
.toList();
searchIndexService.batchIndex(documents);
}
@Override
@ -52,30 +55,7 @@ public class PostgresSearchRebuildService implements SearchRebuildService {
return;
}
Skill skill = skillOpt.get();
Optional<Namespace> namespaceOpt = namespaceRepository.findById(skill.getNamespaceId());
if (namespaceOpt.isEmpty()) {
return;
}
Namespace namespace = namespaceOpt.get();
String searchText = buildSearchText(skill);
SkillSearchDocument document = new SkillSearchDocument(
skill.getId(),
skill.getNamespaceId(),
namespace.getSlug(),
skill.getOwnerId(),
skill.getDisplayName() != null ? skill.getDisplayName() : skill.getSlug(),
skill.getSummary(),
"", // keywords - could be extracted from metadata
searchText,
skill.getVisibility().name(),
skill.getStatus().name()
);
searchIndexService.index(document);
toDocument(skillOpt.get()).ifPresent(searchIndexService::index);
}
private String buildSearchText(Skill skill) {
@ -89,4 +69,27 @@ public class PostgresSearchRebuildService implements SearchRebuildService {
}
return sb.toString().trim();
}
private Optional<SkillSearchDocument> toDocument(Skill skill) {
Optional<Namespace> namespaceOpt = namespaceRepository.findById(skill.getNamespaceId());
if (namespaceOpt.isEmpty()) {
return Optional.empty();
}
Namespace namespace = namespaceOpt.get();
String searchText = buildSearchText(skill);
return Optional.of(new SkillSearchDocument(
skill.getId(),
skill.getNamespaceId(),
namespace.getSlug(),
skill.getOwnerId(),
skill.getDisplayName() != null ? skill.getDisplayName() : skill.getSlug(),
skill.getSummary(),
"",
searchText,
skill.getVisibility().name(),
skill.getStatus().name()
));
}
}