From 0a413be803d4469ce38ac541b20fcfb7f1fbe98f Mon Sep 17 00:00:00 2001 From: zk Date: Mon, 13 Jul 2026 21:40:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AF=84=E5=88=86=E6=A0=87?= =?UTF-8?q?=E5=87=86=20=20=E5=9B=9E=E6=BB=9A=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pojo/dto/job/JobMatchScoreDto.java | 3 + .../java/org/jiayunet/service/JobService.java | 10 +- .../org/jiayunet/pojo/po/JobCategory.java | 4 +- .../org/jiayunet/pojo/vo/JobListItemVo.java | 3 + .../org/jiayunet/service/JobMatchService.java | 155 +++++++++++++++++- .../src/main/resources/mapper/JobMapper.xml | 2 + 6 files changed, 165 insertions(+), 12 deletions(-) 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 @@ +