diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/job/JobMatchScoreDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/job/JobMatchScoreDto.java
index 92840e9..f6da944 100644
--- a/client-api/src/main/java/org/jiayunet/pojo/dto/job/JobMatchScoreDto.java
+++ b/client-api/src/main/java/org/jiayunet/pojo/dto/job/JobMatchScoreDto.java
@@ -22,4 +22,7 @@ public class JobMatchScoreDto {
/** 经历得分(0-100,百分制) */
private Integer experienceScore;
+
+ /** 方向适配得分(0-100,百分制,基于AI推荐岗位方向+行业方向与岗位的匹配) */
+ private Integer fitScore;
}
diff --git a/client-api/src/main/java/org/jiayunet/service/JobService.java b/client-api/src/main/java/org/jiayunet/service/JobService.java
index 00e542b..c376800 100644
--- a/client-api/src/main/java/org/jiayunet/service/JobService.java
+++ b/client-api/src/main/java/org/jiayunet/service/JobService.java
@@ -148,12 +148,12 @@ public class JobService {
dto.setStatus(vo.getStatus());
Map 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> matchScoreMap = jobMatchService.batchCalculateMatchScore(jobList, userId);
Map 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;
}
diff --git a/manager/src/main/java/org/jiayunet/pojo/po/JobCategory.java b/manager/src/main/java/org/jiayunet/pojo/po/JobCategory.java
index c4249ac..579737a 100644
--- a/manager/src/main/java/org/jiayunet/pojo/po/JobCategory.java
+++ b/manager/src/main/java/org/jiayunet/pojo/po/JobCategory.java
@@ -7,7 +7,7 @@ import lombok.Data;
/**
* 岗位类型表(bg_job_category)
- * 树形结构,一级/二级分类
+ * 树形结构,一级/二级/三级分类(叶子为三级)
*
* @author zk
*/
@@ -27,6 +27,6 @@ public class JobCategory {
/** 父级ID,0=顶级 */
private Long parentId;
- /** 层级 1=一级 2=二级 */
+ /** 层级 1=一级 2=二级 3=三级 */
private Integer level;
}
diff --git a/manager/src/main/java/org/jiayunet/pojo/vo/JobListItemVo.java b/manager/src/main/java/org/jiayunet/pojo/vo/JobListItemVo.java
index 2cb3f48..8e88506 100644
--- a/manager/src/main/java/org/jiayunet/pojo/vo/JobListItemVo.java
+++ b/manager/src/main/java/org/jiayunet/pojo/vo/JobListItemVo.java
@@ -59,6 +59,9 @@ public class JobListItemVo {
/** 岗位类型名称 */
private String categoryName;
+ /** 公司行业ID(二级叶子,用于方向匹配 fitScore) */
+ private Long companyIndustryId;
+
/** 岗位标签 */
@TableField(typeHandler = JacksonTypeHandler.class)
private List tags;
diff --git a/manager/src/main/java/org/jiayunet/service/JobMatchService.java b/manager/src/main/java/org/jiayunet/service/JobMatchService.java
index 43bb3e6..68179d2 100644
--- a/manager/src/main/java/org/jiayunet/service/JobMatchService.java
+++ b/manager/src/main/java/org/jiayunet/service/JobMatchService.java
@@ -14,10 +14,11 @@ import java.util.stream.Collectors;
/**
* 岗位匹配度计算服务
- * 主要功能:根据用户简历和岗位信息,计算教育/经历/技能三维度匹配分
+ * 主要功能:根据用户简历和岗位信息,计算方向/教育/经历/技能四维度匹配分
* 依赖:无
* 使用表:bg_user_profile(查询用户简历维度数据)、bg_job_skill_tag_relation(查询岗位技能)、
- * bg_user_profile_skill_tag_relation(查询用户技能)、bg_major_category(专业树形匹配)
+ * bg_user_profile_skill_tag_relation(查询用户技能)、bg_major_category(专业树形匹配)、
+ * bg_user_job_intention(AI推荐岗位/行业方向)、bg_job_category(岗位类型树形匹配)、bg_industry(行业树形匹配)
*
* @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;
+
/**
* 批量计算岗位匹配度
- * 1. 查询用户简历 2. 查询用户技能 3. 批量查询岗位技能 4. 批量查询专业信息 5. 逐个计算三维度分数 6. 加权计算总分
+ * 1. 查询用户简历 2. 查询用户技能 3. 批量查询岗位技能 4. 批量查询专业信息
+ * 5. 查询求职意向(AI推荐方向)并加载类型/行业节点 6. 逐个计算四维度分数 7. 加权计算总分
*/
public Map> batchCalculateMatchScore(List jobs, Long userId) {
if (jobs == null || jobs.isEmpty()) {
@@ -64,20 +75,29 @@ public class JobMatchService {
// 4. 批量查询专业信息(用于树形匹配)
Map majorMap = loadMajorMap(profile, jobs);
- // 5. 逐个计算匹配度
+ // 5. 查询求职意向(取 AI 推荐岗位方向/行业方向),并加载相关类型/行业节点
+ UserJobIntention intention = userJobIntentionMapper.selectOne(new LambdaQueryWrapper().eq(UserJobIntention::getUserId, userId));
+ List aiCategoryIds = intention != null ? intention.getAiCategoryIds() : null;
+ List aiIndustryIds = intention != null ? intention.getAiIndustryIds() : null;
+ Map categoryMap = loadCategoryMap(aiCategoryIds, jobs);
+ Map industryMap = loadIndustryMap(aiIndustryIds, jobs);
+
+ // 6. 逐个计算匹配度
Map> 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 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 loadCategoryMap(List aiCategoryIds, List jobs) {
+ Set 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 categories = jobCategoryMapper.selectBatchIds(ids);
+ return categories.stream().collect(Collectors.toMap(JobCategory::getId, c -> c));
+ }
+
+ /**
+ * 批量加载行业节点Map(AI推荐行业 + 公司行业,用于层级匹配)
+ */
+ private Map loadIndustryMap(List aiIndustryIds, List jobs) {
+ Set 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 industries = industryMapper.selectBatchIds(ids);
+ return industries.stream().collect(Collectors.toMap(Industry::getId, i -> i));
+ }
+
+ /**
+ * 计算方向适配得分 fitScore(百分制)
+ * 基于 AI 推荐的岗位方向/行业方向与岗位实际类型/公司行业做层级匹配
+ * fitScore = 岗位类型分×70% + 行业分×30%
+ * 用户未做过 AI 分析(无 AI 推荐方向)时返回 0
+ */
+ private int calculateFitScore(JobListItemVo job, List aiCategoryIds, List aiIndustryIds,
+ Map categoryMap, Map 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 推荐中的最高分)
+ * 同三级→100,同二级→70,同一级→30,否则→0
+ */
+ private int calculateCategoryFit(List aiCategoryIds, Long jobCategoryId, Map 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 推荐中的最高分)
+ * 同二级→100,同一级→60,否则→0
+ */
+ private int calculateIndustryFit(List aiIndustryIds, Long companyIndustryId, Map 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
*/
diff --git a/manager/src/main/resources/mapper/JobMapper.xml b/manager/src/main/resources/mapper/JobMapper.xml
index bb90ca3..9372edd 100644
--- a/manager/src/main/resources/mapper/JobMapper.xml
+++ b/manager/src/main/resources/mapper/JobMapper.xml
@@ -27,6 +27,7 @@
+