Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -240,6 +240,7 @@ public class AppJobController extends BaseController
|
||||
String errorMsg = "描述中包含敏感词:" + String.join("、", sensitiveWords);
|
||||
return AjaxResult.error(errorMsg, sensitiveWords);
|
||||
}
|
||||
job.setDataSource("1");//系统自录
|
||||
jobService.publishJob(job);
|
||||
return success();
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@ public class CmsJobController extends BaseController
|
||||
return AjaxResult.error(errorMsg, sensitiveWords);
|
||||
}
|
||||
job.setJobStatus("0");
|
||||
job.setDataSource("1");//系统自录
|
||||
// 无敏感词,执行插入
|
||||
return toAjax(jobService.insertJob(job));
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ public class Job extends BaseEntity
|
||||
@ApiModelProperty("是否发布 0未发布 1发布")
|
||||
private Integer isPublish;
|
||||
|
||||
@ApiModelProperty("数据来源 5本地石河子模板导入")
|
||||
@ApiModelProperty("数据来源 5本地石河子模板导入;本地录入1;互联网岗位上传2;")
|
||||
private String dataSource;
|
||||
|
||||
@ApiModelProperty("岗位链接")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1011,6 +1011,7 @@ public class JobServiceImpl extends ServiceImpl<JobMapper,Job> implements IJobSe
|
||||
job.setCreateBy(string);
|
||||
job.setCreateTime(formattedDate);
|
||||
job.setRowId(Long.valueOf(rowWork.getId()));
|
||||
job.setDataSource("2");//互联网岗位上传
|
||||
|
||||
jobBatch.add(job);
|
||||
|
||||
|
||||
@@ -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 Hash:field 是 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));
|
||||
}
|
||||
}
|
||||
@@ -44,16 +44,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="missedOutdoorFairCount" column="missed_outdoor_fair_count" />
|
||||
|
||||
<collection property="companyContactList" ofType="com.ruoyi.common.core.domain.entity.CompanyContact"
|
||||
resultMap="CompanyContactResult"/>
|
||||
select="selectCompanyContactsById"
|
||||
column="company_id"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap type="com.ruoyi.common.core.domain.entity.CompanyContact" id="CompanyContactResult">
|
||||
<result property="contactPerson" column="con_contact_person"/>
|
||||
<result property="contactPersonPhone" column="con_contact_person_phone"/>
|
||||
<result property="companyId" column="con_company_id" />
|
||||
<result property="id" column="con_company_contact_id" />
|
||||
<result property="id" column="id" />
|
||||
<result property="companyId" column="company_id" />
|
||||
<result property="contactPerson" column="contact_person"/>
|
||||
<result property="contactPersonPhone" column="contact_person_phone"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectCompanyContactsById" resultMap="CompanyContactResult">
|
||||
select id, company_id, contact_person, contact_person_phone
|
||||
from company_contact
|
||||
where company_id = #{companyId}
|
||||
and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<sql id="selectCompanyVo">
|
||||
select company_id, name, location, industry, scale, del_flag, create_by, create_time, update_by, update_time, remark,code,description,nature,company_nature,total_recruitment,registered_address,contact_person,contact_person_phone,is_abnormal,not_pass_reason,status,case when status='2' then update_time else null end reject_time,is_imp_company,imp_company_type,enterprise_type,legal_person,legal_id_card,legal_phone,is_hrs,
|
||||
(select count(1)
|
||||
@@ -155,10 +163,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and fc.del_flag = '0'
|
||||
and fc.review_status = '1'
|
||||
and f.end_time < CURRENT_TIMESTAMP
|
||||
and coalesce(a.attendance_status, '0') <> '1') as missed_outdoor_fair_count,
|
||||
cct.contact_person as con_contact_person,cct.contact_person_phone as con_contact_person_phone,
|
||||
cct.company_id as con_company_id,cct.id as con_company_contact_id from company cp
|
||||
left join company_contact cct on cp.company_id=cct.company_id and cct.del_flag='0'
|
||||
and coalesce(a.attendance_status, '0') <> '1') as missed_outdoor_fair_count
|
||||
from company cp
|
||||
<where> cp.del_flag = '0'
|
||||
<if test="name != null and name != ''"> and cp.name like concat('%', cast(#{name, jdbcType=VARCHAR} as varchar), '%')</if>
|
||||
<if test="location != null and location != ''"> and cp.location = #{location}</if>
|
||||
@@ -250,9 +256,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and fc.review_status = '1'
|
||||
and f.end_time < CURRENT_TIMESTAMP
|
||||
and coalesce(a.attendance_status, '0') <> '1') as missed_outdoor_fair_count,
|
||||
su.lc_userid,su.user_id,cct.contact_person as con_contact_person,cct.contact_person_phone as con_contact_person_phone,cct.company_id as con_company_id from company t
|
||||
su.lc_userid,su.user_id from company t
|
||||
left join sys_user su on t.code=su.id_card and su.del_flag='0'
|
||||
left join company_contact cct on t.company_id=cct.company_id and cct.del_flag='0'
|
||||
<where> t.del_flag = '0' and su.lc_userid is not null
|
||||
<if test="name != null and name != ''"> and t.name like concat('%', cast(#{name, jdbcType=VARCHAR} as varchar), '%')</if>
|
||||
<if test="scale != null and scale != ''"> and t.scale = #{scale}</if>
|
||||
|
||||
Reference in New Issue
Block a user