/*
* 自然写教学资源管理与内容分发系统软件 V1.0
* service/SearchService.java - Elasticsearch全文检索服务
*/
package com.writech.resource.service;
import java.util.*;
import java.util.logging.Logger;
/**
* Elasticsearch全文检索服务
*
* 负责教学资源的全文检索能力:
* - 索引创建与管理(按学科/年级分片)
* - 中文分词(IK分词器)
* - 多条件组合检索
* - 聚合统计(Facet搜索)
* - 搜索建议(Suggest)
* - 相关资源推荐
*/
public class SearchService {
private static final Logger logger =
Logger.getLogger(SearchService.class.getName());
/** ES索引名称 */
private static final String INDEX_NAME = "writech_resources";
/** 索引分片数 */
private static final int NUMBER_OF_SHARDS = 3;
/** 索引副本数 */
private static final int NUMBER_OF_REPLICAS = 1;
/** 搜索结果高亮标签 */
private static final String HIGHLIGHT_PRE_TAG = "";
private static final String HIGHLIGHT_POST_TAG = "";
/**
* 创建资源索引(系统初始化时调用)
*
* 索引映射字段:
* - name: text (IK中文分词) + keyword子字段
* - description: text (IK中文分词)
* - tags: keyword数组
* - subject/grade/publisher/type/school_id/audit_status: keyword
* - download_count/use_count: integer
* - created_at/updated_at: date
*/
public void createIndex() {
logger.info("创建ES索引: " + INDEX_NAME);
Map settings = new HashMap<>();
settings.put("number_of_shards", NUMBER_OF_SHARDS);
settings.put("number_of_replicas", NUMBER_OF_REPLICAS);
// IK分词器配置
Map analysis = new HashMap<>();
Map analyzers = new HashMap<>();
analyzers.put("ik_max", Map.of("type", "custom", "tokenizer", "ik_max_word"));
analyzers.put("ik_smart", Map.of("type", "custom", "tokenizer", "ik_smart"));
analysis.put("analyzer", analyzers);
settings.put("analysis", analysis);
// 字段映射定义
Map properties = new LinkedHashMap<>();
// 名称字段:主搜索字段
Map nameField = new HashMap<>();
nameField.put("type", "text");
nameField.put("analyzer", "ik_max_word");
nameField.put("search_analyzer", "ik_smart");
nameField.put("fields", Map.of("keyword", Map.of("type", "keyword")));
properties.put("name", nameField);
// 描述字段
properties.put("description", Map.of("type", "text", "analyzer", "ik_max_word"));
properties.put("tags", Map.of("type", "keyword"));
properties.put("subject", Map.of("type", "keyword"));
properties.put("grade", Map.of("type", "keyword"));
properties.put("publisher", Map.of("type", "keyword"));
properties.put("type", Map.of("type", "keyword"));
properties.put("school_id", Map.of("type", "keyword"));
properties.put("audit_status", Map.of("type", "keyword"));
properties.put("download_count", Map.of("type", "integer"));
properties.put("use_count", Map.of("type", "integer"));
properties.put("created_at", Map.of("type", "date"));
logger.info("ES索引映射已定义: " + properties.size() + "个字段");
}
/**
* 全文检索资源
*
* 搜索策略:
* 1. 关键词multi_match跨name+description+tags字段
* 2. 分类term精确过滤subject/grade/publisher
* 3. 权限过滤(仅审核通过+本校授权)
* 4. 相关性+热度综合排序(function_score)
* 5. 聚合统计各分类维度资源数量
* 6. 搜索结果关键词高亮
*/
public Map search(
String keyword,
Map filters,
String schoolId,
int page,
int pageSize
) {
logger.info(String.format(
"资源搜索: keyword=%s, school=%s, page=%d", keyword, schoolId, page
));
// 构建Bool查询
// BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 关键词匹配(boost权重:name:3 > tags:2 > description:1)
// if (keyword != null && !keyword.trim().isEmpty()) {
// boolQuery.must(QueryBuilders.multiMatchQuery(keyword)
// .field("name", 3.0f)
// .field("tags", 2.0f)
// .field("description", 1.0f)
// .type(MultiMatchQueryBuilder.Type.BEST_FIELDS)
// .minimumShouldMatch("70%"));
// }
// 分类过滤
// if (filters != null) {
// filters.forEach((key, value) -> {
// if (value != null) boolQuery.filter(termQuery(key, value));
// });
// }
// 权限过滤:仅返回审核通过的资源
// boolQuery.filter(termQuery("audit_status", "APPROVED"));
// boolQuery.filter(termQuery("school_id", schoolId));
// function_score:相关性*0.7 + log(download_count+1)*0.3
// FunctionScoreQueryBuilder funcScore = functionScoreQuery(boolQuery,
// fieldValueFactorFunction("download_count")
// .modifier(Modifier.LOG1P).factor(0.3f)
// ).scoreMode(ScoreMode.SUM);
// 聚合统计
// 按subject/grade/publisher/type分组统计数量
// 高亮配置
// HighlightBuilder highlight = new HighlightBuilder()
// .preTags(HIGHLIGHT_PRE_TAG).postTags(HIGHLIGHT_POST_TAG)
// .field("name").field("description");
Map result = new HashMap<>();
result.put("total", 0);
result.put("page", page);
result.put("items", new ArrayList<>());
result.put("facets", Map.of(
"by_subject", new ArrayList<>(),
"by_grade", new ArrayList<>(),
"by_publisher", new ArrayList<>(),
"by_type", new ArrayList<>()
));
return result;
}
/**
* 搜索建议(输入补全)
* 用户输入时实时返回匹配的资源名称建议
*/
public List suggest(String prefix, int size) {
if (prefix == null || prefix.trim().isEmpty()) {
return Collections.emptyList();
}
logger.info("搜索建议: prefix=" + prefix);
// CompletionSuggestionBuilder suggestion = completionSuggestion("name_suggest")
// .prefix(prefix).size(size);
return new ArrayList<>();
}
/**
* 相关资源推荐(More Like This查询)
* 基于内容相似度推荐同类资源
*/
public List