修改评分标准 回滚点
This commit is contained in:
@@ -22,4 +22,7 @@ public class JobMatchScoreDto {
|
||||
|
||||
/** 经历得分(0-100,百分制) */
|
||||
private Integer experienceScore;
|
||||
|
||||
/** 方向适配得分(0-100,百分制,基于AI推荐岗位方向+行业方向与岗位的匹配) */
|
||||
private Integer fitScore;
|
||||
}
|
||||
|
||||
@@ -148,12 +148,12 @@ public class JobService {
|
||||
dto.setStatus(vo.getStatus());
|
||||
Map<String, Integer> scoreMap = matchScoreMap.get(vo.getId());
|
||||
if (scoreMap != null) {
|
||||
JobMatchScoreDto matchScore = new JobMatchScoreDto(scoreMap.get("educationScore"), scoreMap.get("skillScore"), scoreMap.get("experienceScore"));
|
||||
JobMatchScoreDto matchScore = new JobMatchScoreDto(scoreMap.get("educationScore"), scoreMap.get("skillScore"), scoreMap.get("experienceScore"), scoreMap.get("fitScore"));
|
||||
dto.setMatchScore(scoreMap.get("totalScore"));
|
||||
dto.setMatchDetail(matchScore);
|
||||
} else {
|
||||
dto.setMatchScore(0);
|
||||
dto.setMatchDetail(new JobMatchScoreDto(0, 0, 0));
|
||||
dto.setMatchDetail(new JobMatchScoreDto(0, 0, 0, 0));
|
||||
}
|
||||
return dto;
|
||||
}).collect(Collectors.toList());
|
||||
@@ -252,6 +252,8 @@ public class JobService {
|
||||
item.setMinExperience(job.getMinExperience());
|
||||
item.setRequiredMajorIds(job.getRequiredMajorIds());
|
||||
item.setMajorSensitivity(job.getMajorSensitivity());
|
||||
item.setCategoryId(job.getCategoryId());
|
||||
item.setCompanyIndustryId(company.getIndustryId());
|
||||
jobList.add(item);
|
||||
Map<Long, Map<String, Integer>> matchScoreMap = jobMatchService.batchCalculateMatchScore(jobList, userId);
|
||||
Map<String, Integer> scoreMap = matchScoreMap.get(jobId);
|
||||
@@ -317,10 +319,10 @@ public class JobService {
|
||||
dto.setIsFavorite(count > 0);
|
||||
if (scoreMap != null) {
|
||||
dto.setMatchScore(scoreMap.get("totalScore"));
|
||||
dto.setMatchDetail(new JobMatchScoreDto(scoreMap.get("educationScore"), scoreMap.get("skillScore"), scoreMap.get("experienceScore")));
|
||||
dto.setMatchDetail(new JobMatchScoreDto(scoreMap.get("educationScore"), scoreMap.get("skillScore"), scoreMap.get("experienceScore"), scoreMap.get("fitScore")));
|
||||
} else {
|
||||
dto.setMatchScore(0);
|
||||
dto.setMatchDetail(new JobMatchScoreDto(0, 0, 0));
|
||||
dto.setMatchDetail(new JobMatchScoreDto(0, 0, 0, 0));
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import lombok.Data;
|
||||
|
||||
/**
|
||||
* 岗位类型表(bg_job_category)
|
||||
* <p>树形结构,一级/二级分类</p>
|
||||
* <p>树形结构,一级/二级/三级分类(叶子为三级)</p>
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@@ -27,6 +27,6 @@ public class JobCategory {
|
||||
/** 父级ID,0=顶级 */
|
||||
private Long parentId;
|
||||
|
||||
/** 层级 1=一级 2=二级 */
|
||||
/** 层级 1=一级 2=二级 3=三级 */
|
||||
private Integer level;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,9 @@ public class JobListItemVo {
|
||||
/** 岗位类型名称 */
|
||||
private String categoryName;
|
||||
|
||||
/** 公司行业ID(二级叶子,用于方向匹配 fitScore) */
|
||||
private Long companyIndustryId;
|
||||
|
||||
/** 岗位标签 */
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<String> tags;
|
||||
|
||||
@@ -14,10 +14,11 @@ import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 岗位匹配度计算服务
|
||||
* <p>主要功能:根据用户简历和岗位信息,计算教育/经历/技能三维度匹配分</p>
|
||||
* <p>主要功能:根据用户简历和岗位信息,计算方向/教育/经历/技能四维度匹配分</p>
|
||||
* <p>依赖:无</p>
|
||||
* <p>使用表:bg_user_profile(查询用户简历维度数据)、bg_job_skill_tag_relation(查询岗位技能)、
|
||||
* bg_user_profile_skill_tag_relation(查询用户技能)、bg_major_category(专业树形匹配)</p>
|
||||
* bg_user_profile_skill_tag_relation(查询用户技能)、bg_major_category(专业树形匹配)、
|
||||
* bg_user_job_intention(AI推荐岗位/行业方向)、bg_job_category(岗位类型树形匹配)、bg_industry(行业树形匹配)</p>
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@@ -37,9 +38,19 @@ public class JobMatchService {
|
||||
@Autowired
|
||||
private MajorCategoryMapper majorCategoryMapper;
|
||||
|
||||
@Autowired
|
||||
private UserJobIntentionMapper userJobIntentionMapper;
|
||||
|
||||
@Autowired
|
||||
private JobCategoryMapper jobCategoryMapper;
|
||||
|
||||
@Autowired
|
||||
private IndustryMapper industryMapper;
|
||||
|
||||
/**
|
||||
* 批量计算岗位匹配度
|
||||
* <p>1. 查询用户简历 2. 查询用户技能 3. 批量查询岗位技能 4. 批量查询专业信息 5. 逐个计算三维度分数 6. 加权计算总分</p>
|
||||
* <p>1. 查询用户简历 2. 查询用户技能 3. 批量查询岗位技能 4. 批量查询专业信息
|
||||
* 5. 查询求职意向(AI推荐方向)并加载类型/行业节点 6. 逐个计算四维度分数 7. 加权计算总分</p>
|
||||
*/
|
||||
public Map<Long, Map<String, Integer>> batchCalculateMatchScore(List<JobListItemVo> jobs, Long userId) {
|
||||
if (jobs == null || jobs.isEmpty()) {
|
||||
@@ -64,20 +75,29 @@ public class JobMatchService {
|
||||
// 4. 批量查询专业信息(用于树形匹配)
|
||||
Map<Long, MajorCategory> majorMap = loadMajorMap(profile, jobs);
|
||||
|
||||
// 5. 逐个计算匹配度
|
||||
// 5. 查询求职意向(取 AI 推荐岗位方向/行业方向),并加载相关类型/行业节点
|
||||
UserJobIntention intention = userJobIntentionMapper.selectOne(new LambdaQueryWrapper<UserJobIntention>().eq(UserJobIntention::getUserId, userId));
|
||||
List<Long> aiCategoryIds = intention != null ? intention.getAiCategoryIds() : null;
|
||||
List<Long> aiIndustryIds = intention != null ? intention.getAiIndustryIds() : null;
|
||||
Map<Long, JobCategory> categoryMap = loadCategoryMap(aiCategoryIds, jobs);
|
||||
Map<Long, Industry> industryMap = loadIndustryMap(aiIndustryIds, jobs);
|
||||
|
||||
// 6. 逐个计算匹配度
|
||||
Map<Long, Map<String, Integer>> result = new HashMap<>();
|
||||
for (JobListItemVo job : jobs) {
|
||||
int educationScore = calculateEducationScore(profile, job, majorMap);
|
||||
int experienceScore = calculateExperienceScore(profile);
|
||||
int skillScore = calculateSkillScore(jobSkillMap.get(job.getId()), userSkillTagSet);
|
||||
int fitScore = calculateFitScore(job, aiCategoryIds, aiIndustryIds, categoryMap, industryMap);
|
||||
|
||||
// 加权计算总分:教育30% + 经历30% + 技能40%
|
||||
int totalScore = (int) Math.round(educationScore * 0.3 + experienceScore * 0.3 + skillScore * 0.4);
|
||||
// 加权计算总分:方向50% + 教育15% + 经历15% + 技能20%
|
||||
int totalScore = (int) Math.round(fitScore * 0.5 + educationScore * 0.15 + experienceScore * 0.15 + skillScore * 0.2);
|
||||
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
map.put("educationScore", educationScore);
|
||||
map.put("experienceScore", experienceScore);
|
||||
map.put("skillScore", skillScore);
|
||||
map.put("fitScore", fitScore);
|
||||
map.put("totalScore", totalScore);
|
||||
result.put(job.getId(), map);
|
||||
}
|
||||
@@ -85,6 +105,129 @@ public class JobMatchService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量加载岗位类型节点Map(AI推荐类型 + 岗位类型,用于层级匹配)
|
||||
*/
|
||||
private Map<Long, JobCategory> loadCategoryMap(List<Long> aiCategoryIds, List<JobListItemVo> jobs) {
|
||||
Set<Long> ids = new HashSet<>();
|
||||
if (aiCategoryIds != null) {
|
||||
ids.addAll(aiCategoryIds);
|
||||
}
|
||||
for (JobListItemVo job : jobs) {
|
||||
if (job.getCategoryId() != null) {
|
||||
ids.add(job.getCategoryId());
|
||||
}
|
||||
}
|
||||
if (ids.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<JobCategory> categories = jobCategoryMapper.selectBatchIds(ids);
|
||||
return categories.stream().collect(Collectors.toMap(JobCategory::getId, c -> c));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量加载行业节点Map(AI推荐行业 + 公司行业,用于层级匹配)
|
||||
*/
|
||||
private Map<Long, Industry> loadIndustryMap(List<Long> aiIndustryIds, List<JobListItemVo> jobs) {
|
||||
Set<Long> ids = new HashSet<>();
|
||||
if (aiIndustryIds != null) {
|
||||
ids.addAll(aiIndustryIds);
|
||||
}
|
||||
for (JobListItemVo job : jobs) {
|
||||
if (job.getCompanyIndustryId() != null) {
|
||||
ids.add(job.getCompanyIndustryId());
|
||||
}
|
||||
}
|
||||
if (ids.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<Industry> industries = industryMapper.selectBatchIds(ids);
|
||||
return industries.stream().collect(Collectors.toMap(Industry::getId, i -> i));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算方向适配得分 fitScore(百分制)
|
||||
* <p>基于 AI 推荐的岗位方向/行业方向与岗位实际类型/公司行业做层级匹配</p>
|
||||
* <p>fitScore = 岗位类型分×70% + 行业分×30%</p>
|
||||
* <p>用户未做过 AI 分析(无 AI 推荐方向)时返回 0</p>
|
||||
*/
|
||||
private int calculateFitScore(JobListItemVo job, List<Long> aiCategoryIds, List<Long> aiIndustryIds,
|
||||
Map<Long, JobCategory> categoryMap, Map<Long, Industry> industryMap) {
|
||||
boolean noCategory = aiCategoryIds == null || aiCategoryIds.isEmpty();
|
||||
boolean noIndustry = aiIndustryIds == null || aiIndustryIds.isEmpty();
|
||||
// 无任何 AI 推荐方向 → 不参与加分
|
||||
if (noCategory && noIndustry) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int categoryScore = calculateCategoryFit(aiCategoryIds, job.getCategoryId(), categoryMap);
|
||||
int industryScore = calculateIndustryFit(aiIndustryIds, job.getCompanyIndustryId(), industryMap);
|
||||
|
||||
return (int) Math.round(categoryScore * 0.7 + industryScore * 0.3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算岗位类型方向匹配分(三级树,取 AI 推荐中的最高分)
|
||||
* <p>同三级→100,同二级→70,同一级→30,否则→0</p>
|
||||
*/
|
||||
private int calculateCategoryFit(List<Long> aiCategoryIds, Long jobCategoryId, Map<Long, JobCategory> categoryMap) {
|
||||
if (aiCategoryIds == null || aiCategoryIds.isEmpty() || jobCategoryId == null) {
|
||||
return 0;
|
||||
}
|
||||
JobCategory jobCategory = categoryMap.get(jobCategoryId);
|
||||
if (jobCategory == null) {
|
||||
return 0;
|
||||
}
|
||||
int maxScore = 0;
|
||||
for (Long aiId : aiCategoryIds) {
|
||||
JobCategory aiCategory = categoryMap.get(aiId);
|
||||
if (aiCategory == null) continue;
|
||||
int score;
|
||||
if (aiCategory.getId().equals(jobCategory.getId())) {
|
||||
score = 100;
|
||||
} else if (aiCategory.getParentId() != null && aiCategory.getParentId().equals(jobCategory.getParentId())) {
|
||||
score = 70;
|
||||
} else if (aiCategory.getRootId() != null && aiCategory.getRootId().equals(jobCategory.getRootId())) {
|
||||
score = 30;
|
||||
} else {
|
||||
score = 0;
|
||||
}
|
||||
maxScore = Math.max(maxScore, score);
|
||||
if (maxScore == 100) return 100;
|
||||
}
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算行业方向匹配分(两级树,取 AI 推荐中的最高分)
|
||||
* <p>同二级→100,同一级→60,否则→0</p>
|
||||
*/
|
||||
private int calculateIndustryFit(List<Long> aiIndustryIds, Long companyIndustryId, Map<Long, Industry> industryMap) {
|
||||
if (aiIndustryIds == null || aiIndustryIds.isEmpty() || companyIndustryId == null) {
|
||||
return 0;
|
||||
}
|
||||
Industry companyIndustry = industryMap.get(companyIndustryId);
|
||||
if (companyIndustry == null) {
|
||||
return 0;
|
||||
}
|
||||
int maxScore = 0;
|
||||
for (Long aiId : aiIndustryIds) {
|
||||
Industry aiIndustry = industryMap.get(aiId);
|
||||
if (aiIndustry == null) continue;
|
||||
int score;
|
||||
if (aiIndustry.getId().equals(companyIndustry.getId())) {
|
||||
score = 100;
|
||||
} else if (aiIndustry.getParentId() != null && aiIndustry.getParentId().equals(companyIndustry.getParentId())) {
|
||||
score = 60;
|
||||
} else {
|
||||
score = 0;
|
||||
}
|
||||
maxScore = Math.max(maxScore, score);
|
||||
if (maxScore == 100) return 100;
|
||||
}
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量加载专业信息Map
|
||||
*/
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<result column="region_name" property="regionName"/>
|
||||
<result column="category_id" property="categoryId"/>
|
||||
<result column="category_name" property="categoryName"/>
|
||||
<result column="company_industry_id" property="companyIndustryId"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectJobPage" resultMap="JobListItemVoMap">
|
||||
@@ -51,6 +52,7 @@
|
||||
c.company_type,
|
||||
c.logo_url AS company_logo_url,
|
||||
c.tags AS company_tags,
|
||||
c.industry_id AS company_industry_id,
|
||||
c.region_code,
|
||||
r.name AS region_name,
|
||||
cat.id AS category_id,
|
||||
|
||||
Reference in New Issue
Block a user