修改评分标准 回滚点

This commit is contained in:
zk
2026-07-13 21:40:52 +08:00
parent 9e01a5aeac
commit 0a413be803
6 changed files with 165 additions and 12 deletions
@@ -22,4 +22,7 @@ public class JobMatchScoreDto {
/** 经历得分(0-100,百分制) */ /** 经历得分(0-100,百分制) */
private Integer experienceScore; private Integer experienceScore;
/** 方向适配得分(0-100,百分制,基于AI推荐岗位方向+行业方向与岗位的匹配) */
private Integer fitScore;
} }
@@ -148,12 +148,12 @@ public class JobService {
dto.setStatus(vo.getStatus()); dto.setStatus(vo.getStatus());
Map<String, Integer> scoreMap = matchScoreMap.get(vo.getId()); Map<String, Integer> scoreMap = matchScoreMap.get(vo.getId());
if (scoreMap != null) { 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.setMatchScore(scoreMap.get("totalScore"));
dto.setMatchDetail(matchScore); dto.setMatchDetail(matchScore);
} else { } else {
dto.setMatchScore(0); dto.setMatchScore(0);
dto.setMatchDetail(new JobMatchScoreDto(0, 0, 0)); dto.setMatchDetail(new JobMatchScoreDto(0, 0, 0, 0));
} }
return dto; return dto;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
@@ -252,6 +252,8 @@ public class JobService {
item.setMinExperience(job.getMinExperience()); item.setMinExperience(job.getMinExperience());
item.setRequiredMajorIds(job.getRequiredMajorIds()); item.setRequiredMajorIds(job.getRequiredMajorIds());
item.setMajorSensitivity(job.getMajorSensitivity()); item.setMajorSensitivity(job.getMajorSensitivity());
item.setCategoryId(job.getCategoryId());
item.setCompanyIndustryId(company.getIndustryId());
jobList.add(item); jobList.add(item);
Map<Long, Map<String, Integer>> matchScoreMap = jobMatchService.batchCalculateMatchScore(jobList, userId); Map<Long, Map<String, Integer>> matchScoreMap = jobMatchService.batchCalculateMatchScore(jobList, userId);
Map<String, Integer> scoreMap = matchScoreMap.get(jobId); Map<String, Integer> scoreMap = matchScoreMap.get(jobId);
@@ -317,10 +319,10 @@ public class JobService {
dto.setIsFavorite(count > 0); dto.setIsFavorite(count > 0);
if (scoreMap != null) { if (scoreMap != null) {
dto.setMatchScore(scoreMap.get("totalScore")); 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 { } else {
dto.setMatchScore(0); dto.setMatchScore(0);
dto.setMatchDetail(new JobMatchScoreDto(0, 0, 0)); dto.setMatchDetail(new JobMatchScoreDto(0, 0, 0, 0));
} }
return dto; return dto;
} }
@@ -7,7 +7,7 @@ import lombok.Data;
/** /**
* 岗位类型表(bg_job_category * 岗位类型表(bg_job_category
* <p>树形结构,一级/二级分类</p> * <p>树形结构,一级/二级/三级分类(叶子为三级)</p>
* *
* @author zk * @author zk
*/ */
@@ -27,6 +27,6 @@ public class JobCategory {
/** 父级ID0=顶级 */ /** 父级ID0=顶级 */
private Long parentId; private Long parentId;
/** 层级 1=一级 2=二级 */ /** 层级 1=一级 2=二级 3=三级 */
private Integer level; private Integer level;
} }
@@ -59,6 +59,9 @@ public class JobListItemVo {
/** 岗位类型名称 */ /** 岗位类型名称 */
private String categoryName; private String categoryName;
/** 公司行业ID(二级叶子,用于方向匹配 fitScore) */
private Long companyIndustryId;
/** 岗位标签 */ /** 岗位标签 */
@TableField(typeHandler = JacksonTypeHandler.class) @TableField(typeHandler = JacksonTypeHandler.class)
private List<String> tags; private List<String> tags;
@@ -14,10 +14,11 @@ import java.util.stream.Collectors;
/** /**
* 岗位匹配度计算服务 * 岗位匹配度计算服务
* <p>主要功能:根据用户简历和岗位信息,计算教育/经历/技能维度匹配分</p> * <p>主要功能:根据用户简历和岗位信息,计算方向/教育/经历/技能维度匹配分</p>
* <p>依赖:无</p> * <p>依赖:无</p>
* <p>使用表:bg_user_profile(查询用户简历维度数据)、bg_job_skill_tag_relation(查询岗位技能)、 * <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_intentionAI推荐岗位/行业方向)、bg_job_category(岗位类型树形匹配)、bg_industry(行业树形匹配)</p>
* *
* @author zk * @author zk
*/ */
@@ -37,9 +38,19 @@ public class JobMatchService {
@Autowired @Autowired
private MajorCategoryMapper majorCategoryMapper; 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) { public Map<Long, Map<String, Integer>> batchCalculateMatchScore(List<JobListItemVo> jobs, Long userId) {
if (jobs == null || jobs.isEmpty()) { if (jobs == null || jobs.isEmpty()) {
@@ -64,20 +75,29 @@ public class JobMatchService {
// 4. 批量查询专业信息(用于树形匹配) // 4. 批量查询专业信息(用于树形匹配)
Map<Long, MajorCategory> majorMap = loadMajorMap(profile, jobs); 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<>(); Map<Long, Map<String, Integer>> result = new HashMap<>();
for (JobListItemVo job : jobs) { for (JobListItemVo job : jobs) {
int educationScore = calculateEducationScore(profile, job, majorMap); int educationScore = calculateEducationScore(profile, job, majorMap);
int experienceScore = calculateExperienceScore(profile); int experienceScore = calculateExperienceScore(profile);
int skillScore = calculateSkillScore(jobSkillMap.get(job.getId()), userSkillTagSet); int skillScore = calculateSkillScore(jobSkillMap.get(job.getId()), userSkillTagSet);
int fitScore = calculateFitScore(job, aiCategoryIds, aiIndustryIds, categoryMap, industryMap);
// 加权计算总分:教育30% + 经历30% + 技能40% // 加权计算总分:方向50% + 教育15% + 经历15% + 技能20%
int totalScore = (int) Math.round(educationScore * 0.3 + experienceScore * 0.3 + skillScore * 0.4); int totalScore = (int) Math.round(fitScore * 0.5 + educationScore * 0.15 + experienceScore * 0.15 + skillScore * 0.2);
Map<String, Integer> map = new HashMap<>(); Map<String, Integer> map = new HashMap<>();
map.put("educationScore", educationScore); map.put("educationScore", educationScore);
map.put("experienceScore", experienceScore); map.put("experienceScore", experienceScore);
map.put("skillScore", skillScore); map.put("skillScore", skillScore);
map.put("fitScore", fitScore);
map.put("totalScore", totalScore); map.put("totalScore", totalScore);
result.put(job.getId(), map); result.put(job.getId(), map);
} }
@@ -85,6 +105,129 @@ public class JobMatchService {
return result; 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 * 批量加载专业信息Map
*/ */
@@ -27,6 +27,7 @@
<result column="region_name" property="regionName"/> <result column="region_name" property="regionName"/>
<result column="category_id" property="categoryId"/> <result column="category_id" property="categoryId"/>
<result column="category_name" property="categoryName"/> <result column="category_name" property="categoryName"/>
<result column="company_industry_id" property="companyIndustryId"/>
</resultMap> </resultMap>
<select id="selectJobPage" resultMap="JobListItemVoMap"> <select id="selectJobPage" resultMap="JobListItemVoMap">
@@ -51,6 +52,7 @@
c.company_type, c.company_type,
c.logo_url AS company_logo_url, c.logo_url AS company_logo_url,
c.tags AS company_tags, c.tags AS company_tags,
c.industry_id AS company_industry_id,
c.region_code, c.region_code,
r.name AS region_name, r.name AS region_name,
cat.id AS category_id, cat.id AS category_id,