忽略本地配置文件

This commit is contained in:
francis-fh
2026-07-29 19:31:57 +08:00
parent 45289d5666
commit 0e675a1004
4 changed files with 4 additions and 1664 deletions

View File

@@ -1,187 +0,0 @@
package com.ruoyi.cms.service.impl;
import com.ruoyi.cms.mapper.JobMapper;
import com.ruoyi.cms.service.IJobTitleSuggestService;
import com.ruoyi.cms.util.JobTitleSuggestTokenizer;
import com.ruoyi.common.core.redis.RedisCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 岗位名称 IK 分词联想服务实现。
*/
@Service
public class JobTitleSuggestServiceImpl implements IJobTitleSuggestService {
private static final Logger logger = LoggerFactory.getLogger(JobTitleSuggestServiceImpl.class);
/**
* Redis Hashfield 是 IK 分词value 是包含该分词的完整岗位名称列表。
*/
private static final String CACHE_KEY = "app:job:title:suggest";
private static final String BUILDING_CACHE_KEY = CACHE_KEY + ":building";
private static final String LOCK_KEY = CACHE_KEY + ":rebuild:lock";
private static final int DEFAULT_LIMIT = 10;
private static final int MAX_LIMIT = 20;
private static final int LOCK_EXPIRE_MINUTES = 30;
@Autowired
private JobMapper jobMapper;
@Autowired
private RedisCache redisCache;
/**
* 确保服务启动后 Redis 中已有一份缓存;后续由 Quartz 定时任务周期性重建。
*/
@PostConstruct
public void init() {
rebuildCache();
}
@Override
public List<String> suggest(String keyword, Integer limit) {
String normalizedKeyword = JobTitleSuggestTokenizer.normalize(keyword);
if (normalizedKeyword.isEmpty()) {
return Collections.emptyList();
}
int resultLimit = normalizeLimit(limit);
try {
Map<String, Object> tokenTitles = redisCache.getCacheMap(CACHE_KEY);
if (tokenTitles == null || tokenTitles.isEmpty()) {
return Collections.emptyList();
}
// Hash field 只来自 IK 分词;这里仅对已有词项做前缀过滤,不生成新的前缀词。
List<Map.Entry<String, Object>> matchedTokens = tokenTitles.entrySet().stream()
.filter(entry -> entry.getKey() != null)
.filter(entry -> entry.getKey().startsWith(normalizedKeyword))
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toList());
Set<String> result = new LinkedHashSet<>();
for (Map.Entry<String, Object> entry : matchedTokens) {
if (result.size() >= resultLimit) {
break;
}
// 先返回匹配到的 IK 分词本身。
result.add(entry.getKey());
// 再返回包含该 IK 分词的完整岗位名称。
appendTitles(result, entry.getValue(), resultLimit);
}
return new ArrayList<>(result).subList(0, Math.min(result.size(), resultLimit));
} catch (Exception e) {
// 联想属于非核心能力Redis 异常时返回空列表,不影响岗位主搜索。
logger.warn("查询岗位名称 IK 分词缓存失败keyword={}", keyword, e);
return Collections.emptyList();
}
}
@Override
public void rebuildCache() {
String lockValue = UUID.randomUUID().toString();
boolean lockAcquired = false;
try {
lockAcquired = redisCache.setIfAbsent(
LOCK_KEY, lockValue, LOCK_EXPIRE_MINUTES, TimeUnit.MINUTES);
if (!lockAcquired) {
logger.info("其他节点正在重建岗位名称 IK 分词缓存,本节点跳过");
return;
}
List<String> sourceList = jobMapper.selectValidJobTitles();
Map<String, List<String>> cacheData = buildTokenTitles(sourceList);
// 先写临时 Hash再原子替换正式 Hash避免接口读到半成品。
redisCache.deleteObject(BUILDING_CACHE_KEY);
if (cacheData.isEmpty()) {
redisCache.deleteObject(CACHE_KEY);
} else {
redisCache.setCacheMap(BUILDING_CACHE_KEY, cacheData);
redisCache.redisTemplate.rename(BUILDING_CACHE_KEY, CACHE_KEY);
}
logger.info("岗位名称 IK 分词缓存重建完成,有效岗位名称{}个IK 词{}个",
sourceList == null ? 0 : sourceList.size(), cacheData.size());
} catch (Exception e) {
logger.error("重建岗位名称 IK 分词缓存失败", e);
} finally {
if (lockAcquired) {
redisCache.deleteObject(LOCK_KEY);
}
}
}
private Map<String, List<String>> buildTokenTitles(List<String> sourceList) {
Map<String, Set<String>> tokenTitles = new TreeMap<>();
if (sourceList == null) {
return Collections.emptyMap();
}
for (String title : sourceList) {
String displayTitle = normalizeDisplayTitle(title);
if (displayTitle.isEmpty()) {
continue;
}
Set<String> tokens = JobTitleSuggestTokenizer.tokenize(displayTitle);
for (String token : tokens) {
tokenTitles.computeIfAbsent(token, key -> new TreeSet<>()).add(displayTitle);
}
}
Map<String, List<String>> result = new LinkedHashMap<>();
for (Map.Entry<String, Set<String>> entry : tokenTitles.entrySet()) {
result.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
return result;
}
private void appendTitles(Set<String> result, Object cachedTitles, int resultLimit) {
if (!(cachedTitles instanceof Iterable)) {
return;
}
for (Object cachedTitle : (Iterable<?>) cachedTitles) {
if (cachedTitle == null || result.size() >= resultLimit) {
break;
}
String title = String.valueOf(cachedTitle);
if (!title.isEmpty()) {
result.add(title);
}
}
}
private String normalizeDisplayTitle(String title) {
if (title == null) {
return "";
}
return title.trim().replaceAll("\\s+", " ");
}
private int normalizeLimit(Integer limit) {
if (limit == null) {
return DEFAULT_LIMIT;
}
return Math.max(1, Math.min(limit, MAX_LIMIT));
}
}