AI 业务逻辑完善

This commit is contained in:
2026-01-21 13:42:38 +08:00
parent 474a71c4db
commit fb05cadd0f
14 changed files with 824 additions and 389 deletions

View File

@@ -3,6 +3,9 @@ package com.vetti.hotake.domain;
import java.math.BigDecimal;
import java.util.List;
import com.vetti.common.core.domain.entity.SysUser;
import com.vetti.hotake.domain.dto.HotakeAiCvScoringRankingDto;
import com.vetti.hotake.domain.dto.HotakeCandidateCompatibilityDto;
import com.vetti.hotake.domain.dto.HotakeCvInfoDto;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -121,6 +124,18 @@ public class HotakeRolesApplyInfo extends BaseEntity
@ApiModelProperty("申请状态pending:进行中complete已完成canceled取消")
private String status;
@ApiModelProperty("AI简历评分和排名JSON")
private String aiCvScoringRankingJson;
@ApiModelProperty("AI简历评分和排名最终得分")
private BigDecimal aiCvScore;
@ApiModelProperty("候选人匹配度JSON")
private String candidateCompatibilityJson;
@ApiModelProperty("候选人匹配度最终得分")
private BigDecimal candidateCompatibilityScore;
@ApiModelProperty("岗位信息")
private HotakeRolesInfo rolesInfo;
@@ -130,6 +145,15 @@ public class HotakeRolesApplyInfo extends BaseEntity
@ApiModelProperty("岗位申请操作记录数据集合")
private List<HotakeRolesApplyOperRecord> applyOperRecords;
@ApiModelProperty("候选人用户信息")
private SysUser candidateUSer;
@ApiModelProperty("AI简历评分和排名数据对象")
private HotakeAiCvScoringRankingDto rankingDto;
@ApiModelProperty("招聘者查看候选人匹配度数据对象")
private HotakeCandidateCompatibilityDto compatibilityDto;
@ApiModelProperty("岗位申请ID数据集合")
private List<Long> applyRoleIdList;

View File

@@ -0,0 +1,51 @@
package com.vetti.hotake.domain.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.util.List;
/**
* 招聘者AI简历评分和排名系统 返回对象
*
* @author ID
* @date 2025-09-06
*/
@Data
@Accessors(chain = true)
public class HotakeAiCvScoringRankingDto {
@ApiModelProperty("技能匹配评分 ")
private BigDecimal skills;
@ApiModelProperty("经验匹配评分 ")
private BigDecimal experience;
@ApiModelProperty("教育背景评分 ")
private BigDecimal education;
@ApiModelProperty("文化契合评分 ")
private BigDecimal cultural;
@ApiModelProperty("发展潜力评分 ")
private BigDecimal potential;
@ApiModelProperty("优势点列表 ")
private List<String> strengths;
@ApiModelProperty("不足点列表 ")
private List<String> weaknesses;
@ApiModelProperty("推荐等级 ")
private String recommendation;
@ApiModelProperty("推荐理由 ")
private String reason;
@ApiModelProperty("总体评分")
private BigDecimal overallScore;
}

View File

@@ -0,0 +1,41 @@
package com.vetti.hotake.domain.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.util.List;
/**
* 招聘者查看候选人匹配度 返回对象
*
* @author ID
* @date 2025-09-06
*/
@Data
@Accessors(chain = true)
public class HotakeCandidateCompatibilityDto {
@ApiModelProperty("核心优势评分 ")
private BigDecimal strengthsScore;
@ApiModelProperty("核心优势列表 ")
private List<String> keyStrengths;
@ApiModelProperty("关键差异评分(越高差异越小) ")
private BigDecimal differencesScore;
@ApiModelProperty("关键差异列表 ")
private List<String> keyDifferences;
@ApiModelProperty("文化契合评分 ")
private BigDecimal culturalScore;
@ApiModelProperty("文化契合详情 ")
private List<String> culturalFitDetails;
@ApiModelProperty("总体评分")
private BigDecimal overallScore;
}

View File

@@ -1,11 +1,10 @@
package com.vetti.hotake.domain.vo;
import com.vetti.hotake.domain.HotakeRolesApplyInfo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 招聘者AI简历评分和排名系统-岗位申请 请求对象
*
@@ -16,6 +15,10 @@ import java.util.List;
@Accessors(chain = true)
public class HotakeAiCvScoringRankingRoleApplyVo {
@ApiModelProperty("岗位申请ID集合")
private List<Long> roleApplyIds;
@ApiModelProperty("岗位申请信息")
private HotakeRolesApplyInfo applyInfo;
/** 简历模版Json */
@ApiModelProperty("简历模版Json")
private String cvTemplateJson;
}

View File

@@ -35,7 +35,7 @@ public class HotakeAiCvScoringRankingVo {
private String educationRequired;
@ApiModelProperty("候选人信息")
private List<HotakeCandidateVcInfoVo> candidates;
private HotakeCandidateVcInfoVo candidates;
}

View File

@@ -27,6 +27,22 @@ public interface HotakeRolesApplyInfoMapper
*/
public List<HotakeRolesApplyInfo> selectHotakeRolesApplyInfoList(HotakeRolesApplyInfo hotakeRolesApplyInfo);
/**
* 查询候选人岗位申请信息列表
*
* @param hotakeRolesApplyInfo 候选人岗位申请信息
* @return 候选人岗位申请信息集合
*/
public List<HotakeRolesApplyInfo> selectHotakeRolesApplyInfoRankingList(HotakeRolesApplyInfo hotakeRolesApplyInfo);
/**
* 查询候选人岗位申请信息列表
*
* @param hotakeRolesApplyInfo 候选人岗位申请信息
* @return 候选人岗位申请信息集合
*/
public List<HotakeRolesApplyInfo> selectHotakeRolesApplyInfoCompatibilityScoreList(HotakeRolesApplyInfo hotakeRolesApplyInfo);
/**
* 新增候选人岗位申请信息
*

View File

@@ -141,7 +141,7 @@ public interface IHotakeAiCommonToolsService {
* @param roleApplyVo 岗位申请数据对象
* @return
*/
public String handleAiCvScoringRanking(HotakeAiCvScoringRankingRoleApplyVo roleApplyVo);
public HotakeAiCvScoringRankingDto handleAiCvScoringRanking(HotakeAiCvScoringRankingRoleApplyVo roleApplyVo);
/**
@@ -149,6 +149,6 @@ public interface IHotakeAiCommonToolsService {
* @param compatibilityVo 招聘者查看候选人匹配度 输入信息
* @return
*/
public String handleCandidateCompatibility(HotakeCandidateCompatibilityVo compatibilityVo);
public HotakeCandidateCompatibilityDto handleCandidateCompatibility(HotakeCandidateCompatibilityVo compatibilityVo);
}

View File

@@ -76,5 +76,20 @@ public interface IHotakeRolesApplyInfoService
*/
public int updateBatchEditStage(List<HotakeRolesApplyStageVo> stageVoList);
/**
* AI简历评分和排名系统
*
* @param hotakeRolesApplyInfo 候选人岗位申请信息
* @return 候选人岗位申请信息集合
*/
public List<HotakeRolesApplyInfo> handleAiCvScoringRankingList(HotakeRolesApplyInfo hotakeRolesApplyInfo);
/**
* 招聘者查看候选人匹配度
*
* @param hotakeRolesApplyInfo 候选人岗位申请信息
* @return 候选人岗位申请信息集合
*/
public HotakeRolesApplyInfo handleCandidateCompatibilityInfo(HotakeRolesApplyInfo hotakeRolesApplyInfo);
}

View File

@@ -2,10 +2,7 @@ package com.vetti.hotake.service.impl;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
@@ -21,9 +18,13 @@ import com.vetti.common.utils.DateUtils;
import com.vetti.common.utils.SecurityUtils;
import com.vetti.common.utils.readFile.FileContentUtil;
import com.vetti.hotake.domain.*;
import com.vetti.hotake.domain.dto.HotakeAiCvScoringRankingDto;
import com.vetti.hotake.domain.dto.HotakeCandidateCompatibilityDto;
import com.vetti.hotake.domain.dto.HotakeCvInfoDto;
import com.vetti.hotake.domain.dto.HotakeInitialQuestionEliminationScoreDto;
import com.vetti.hotake.domain.dto.VcDto.*;
import com.vetti.hotake.domain.vo.HotakeAiCvScoringRankingRoleApplyVo;
import com.vetti.hotake.domain.vo.HotakeCandidateCompatibilityVo;
import com.vetti.hotake.domain.vo.HotakeInitScreQuestionsReplyRecordInfoVo;
import com.vetti.hotake.domain.vo.HotakeInitialQuestionEliminationScoreVo;
import com.vetti.hotake.mapper.HotakeCvInfoMapper;
@@ -192,65 +193,13 @@ public class HotakeInitScreQuestionsReplyRecordInfoServiceImpl extends BaseServi
eliminationScoreVo.setInitScreQuestionsReplyRecordInfoList(initScreQuestionsReplyRecordInfoVo.getInitScreQuestionsReplyRecordInfoList());
HotakeInitialQuestionEliminationScoreDto eliminationScoreDto = aiCommonToolsService.getInitialQuestionEliminationScore(eliminationScoreVo);
//查询候选人的当前最新简历信息
// HotakeCvInfo queryCv = new HotakeCvInfo();
// queryCv.setUserId(SecurityUtils.getUserId());
// queryCv.setCvFileType("cv");
// List<HotakeCvInfo> cvInfos = hotakeCvInfoMapper.selectHotakeCvInfoList(queryCv);
// HotakeCvInfo cvInfo = null;
// if(CollectionUtil.isNotEmpty(cvInfos)) {
// cvInfo = cvInfos.get(0);
// }
// //解析简历内容以及获取简历对应的评分数据
// log.info("开始处理简历");
// try {
// InputStream inputStream = minioClient.getObject(
// GetObjectArgs.builder()
// .bucket(MinioBucketNameEnum.CV.getCode())
// .object(applyInfo.getCvFile())
// .build());
// String contents = FileContentUtil.readFileContent(inputStream, applyInfo.getCvFileSuffix());
// log.info("简历信息:{}", contents);
// //验证文件内容是否改变,如果未改变不进行模型解析直接返回原有结果
// String md5Hash = MD5.create().digestHex16(contents);
// String scoreStr = "";
// if (StrUtil.isNotEmpty(md5Hash) && cvInfo != null && md5Hash.equals(cvInfo.getCvMd5())) {
// //直接获取简历表中的简历解析的详细数据
// applyInfo.setCvScore(cvInfo.getCvScore());
// applyInfo.setCvTemplateJson(cvInfo.getCvTemplateJson());
// fill(FillTypeEnum.UPDATE.getCode(), applyInfo);
// applyInfo.setCvMd5(md5Hash);
// applyInfo.setExperience(cvInfo.getExperience());
// scoreStr = cvInfo.getCvScore();
// }else{
// //生成简历模版数据信息
// HotakeCvInfoDto cvInfoDto = handleAnalysisCvInfo(contents);
// //生成对应的简历评分
// String resultMsg = handleHotakeCvInfoScore(cvInfoDto);
// applyInfo.setCvScore(resultMsg);
// applyInfo.setCvTemplateJson(JSONUtil.toJsonStr(cvInfoDto));
// fill(FillTypeEnum.UPDATE.getCode(), cvInfo);
// applyInfo.setCvMd5(md5Hash);
// applyInfo.setExperience(cvInfoDto.getExperienceYear());
// scoreStr = resultMsg;
// }
//更新岗位申请数据记录--根据评分进行计算
//分数解析
// String[] strs = scoreStr.split("\n");
// if(strs != null && strs.length > 0){
// String score = strs[0].replaceAll("Score:","").trim();
// String[] scores = score.split("/");
// if(scores != null && scores.length > 0){
// applyInfo.setAiMatchScore(scores[0]);
try {
applyInfo.setAiMatchScorePercentage(new BigDecimal(eliminationScoreDto.getScore()).
divide(new BigDecimal(100)).setScale(2, 4));
} catch (Exception e) {
e.printStackTrace();
}
// }
// }
if (applyInfo.getAiMatchScorePercentage() != null) {
if (applyInfo.getAiMatchScorePercentage().compareTo(new BigDecimal(0.85)) >= 0) {
applyInfo.setCandidateStatus(CandidateStatusEnum.HOT.getCode());
@@ -264,6 +213,73 @@ public class HotakeInitScreQuestionsReplyRecordInfoServiceImpl extends BaseServi
applyInfo.setCandidateStatus(CandidateStatusEnum.PENDING.getCode());
}
}
//解析简历数据
//查询候选人的当前最新简历信息
HotakeCvInfo queryCv = new HotakeCvInfo();
queryCv.setUserId(SecurityUtils.getUserId());
queryCv.setCvFileType("cv");
List<HotakeCvInfo> cvInfos = hotakeCvInfoMapper.selectHotakeCvInfoList(queryCv);
HotakeCvInfo cvInfo = null;
if(CollectionUtil.isNotEmpty(cvInfos)) {
cvInfo = cvInfos.get(0);
}
//解析简历内容以及获取简历对应的评分数据
log.info("开始处理简历");
try {
InputStream inputStream = minioClient.getObject(
GetObjectArgs.builder()
.bucket(MinioBucketNameEnum.CV.getCode())
.object(applyInfo.getCvFile())
.build());
String contents = FileContentUtil.readFileContent(inputStream, applyInfo.getCvFileSuffix());
log.info("简历信息:{}", contents);
//验证文件内容是否改变,如果未改变不进行模型解析直接返回原有结果
String md5Hash = MD5.create().digestHex16(contents);
String scoreStr = "";
if (StrUtil.isNotEmpty(md5Hash) && cvInfo != null && md5Hash.equals(cvInfo.getCvMd5())) {
//直接获取简历表中的简历解析的详细数据
applyInfo.setCvScore(cvInfo.getCvScore());
applyInfo.setCvTemplateJson(cvInfo.getCvTemplateJson());
applyInfo.setCvMd5(md5Hash);
applyInfo.setExperience(cvInfo.getExperience());
} else {
//生成简历模版数据信息
HotakeCvInfoDto cvInfoDto = handleAnalysisCvInfo(contents);
//生成对应的简历评分
applyInfo.setCvTemplateJson(JSONUtil.toJsonStr(cvInfoDto));
applyInfo.setCvMd5(md5Hash);
applyInfo.setExperience(cvInfoDto.getExperienceYear());
}
}catch (Exception e) {
e.printStackTrace();
}
//AI简历评分和排名系统-直接传到简历系统中
HotakeAiCvScoringRankingRoleApplyVo roleApplyVo = new HotakeAiCvScoringRankingRoleApplyVo();
roleApplyVo.setApplyInfo(applyInfo);
roleApplyVo.setCvTemplateJson(applyInfo.getCvTemplateJson());
HotakeAiCvScoringRankingDto scoringRankingDto = aiCommonToolsService.handleAiCvScoringRanking(roleApplyVo);
applyInfo.setAiCvScoringRankingJson(JSONUtil.toJsonStr(scoringRankingDto));
applyInfo.setAiCvScore(scoringRankingDto.getOverallScore());
//招聘者查看候选人匹配度
HotakeCandidateCompatibilityVo compatibilityVo = new HotakeCandidateCompatibilityVo();
compatibilityVo.setPosition(rolesInf.getRoleName());
HotakeCvInfoDto cvInfoDto = JSONUtil.toBean(applyInfo.getCvTemplateJson(), HotakeCvInfoDto.class);
compatibilityVo.setCurrentPosition(cvInfoDto.getPosition());
compatibilityVo.setOverallRating(scoringRankingDto.getOverallScore().toString());
compatibilityVo.setWorkExperience(cvInfoDto.getExperienceYear());
List<String> skills = new ArrayList<>();
if(CollectionUtil.isNotEmpty(cvInfoDto.getSkillsTools())){
for (VcSkillsToolsDto toolsDto : cvInfoDto.getSkillsTools()){
skills.add(toolsDto.getContent());
}
}
compatibilityVo.setSkills(skills);
HotakeCandidateCompatibilityDto compatibilityDto = aiCommonToolsService.handleCandidateCompatibility(compatibilityVo);
applyInfo.setCandidateCompatibilityJson(JSONUtil.toJsonStr(compatibilityDto));
applyInfo.setCandidateCompatibilityScore(compatibilityDto.getOverallScore());
applyInfo.setRecruiterId(rolesInf.getRecruiterId());
applyInfo.setStage(StageEnum.APPLIED.getCode());
hotakeRolesApplyInfoMapper.updateHotakeRolesApplyInfo(applyInfo);
@@ -274,11 +290,6 @@ public class HotakeInitScreQuestionsReplyRecordInfoServiceImpl extends BaseServi
hotakeRolesApplyOperRecord.setRoleApplyId(applyInfo.getId());
hotakeRolesApplyOperRecord.setRoleId(rolesInf.getId());
rolesApplyOperRecordService.insertHotakeRolesApplyOperRecord(hotakeRolesApplyOperRecord);
// } catch (Exception e) {
// e.printStackTrace();
// }
return 0;
}
return 0;
}

View File

@@ -4,13 +4,17 @@ import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.vetti.common.core.domain.entity.SysUser;
import com.vetti.common.core.service.BaseServiceImpl;
import com.vetti.common.enums.FillTypeEnum;
import com.vetti.common.enums.StageEnum;
import com.vetti.common.exception.ServiceException;
import com.vetti.common.utils.SecurityUtils;
import com.vetti.hotake.domain.HotakeRolesApplyInfo;
import com.vetti.hotake.domain.HotakeRolesApplyOperRecord;
import com.vetti.hotake.domain.HotakeRolesInfo;
import com.vetti.hotake.domain.dto.HotakeAiCvScoringRankingDto;
import com.vetti.hotake.domain.dto.HotakeCandidateCompatibilityDto;
import com.vetti.hotake.domain.dto.HotakeCvInfoDto;
import com.vetti.hotake.domain.dto.VcDto.*;
import com.vetti.hotake.domain.vo.HotakeRolesApplyStageVo;
@@ -18,6 +22,7 @@ import com.vetti.hotake.mapper.HotakeRolesApplyInfoMapper;
import com.vetti.hotake.mapper.HotakeRolesInfoMapper;
import com.vetti.hotake.service.IHotakeRolesApplyInfoService;
import com.vetti.hotake.service.IHotakeRolesApplyOperRecordService;
import com.vetti.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -43,6 +48,9 @@ public class HotakeRolesApplyInfoServiceImpl extends BaseServiceImpl implements
@Autowired
private IHotakeRolesApplyOperRecordService rolesApplyOperRecordService;
@Autowired
private ISysUserService userService;
/**
* 查询候选人岗位申请信息
*
@@ -371,4 +379,77 @@ public class HotakeRolesApplyInfoServiceImpl extends BaseServiceImpl implements
return resultNum;
}
/**
* AI简历评分和排名系统
* @param hotakeRolesApplyInfo 候选人岗位申请信息
* @return
*/
@Override
public List<HotakeRolesApplyInfo> handleAiCvScoringRankingList(HotakeRolesApplyInfo hotakeRolesApplyInfo) {
if(CollectionUtil.isEmpty(hotakeRolesApplyInfo.getApplyRoleIdList())){
throw new ServiceException("Please select at least one piece of business data");
}
List<HotakeRolesApplyInfo> applyInfoList = hotakeRolesApplyInfoMapper.selectHotakeRolesApplyInfoRankingList(hotakeRolesApplyInfo);
if(CollectionUtil.isNotEmpty(applyInfoList)) {
//查询岗位数据集合
HotakeRolesInfo query = new HotakeRolesInfo();
List<HotakeRolesInfo> rolesInfoList = hotakeRolesInfoMapper.selectHotakeRolesInfoList(query);
List<SysUser> sysUserList = userService.selectUserList(new SysUser());
for(HotakeRolesApplyInfo applyInfo : applyInfoList) {
if(applyInfo.getRoleId() != null){
List<HotakeRolesInfo> rolesInfos = rolesInfoList.stream().filter(e->e.getId().longValue() == applyInfo.getRoleId().longValue()).toList();
if(CollectionUtil.isNotEmpty(rolesInfos)) {
applyInfo.setRolesInfo(rolesInfos.get(0));
}
}
if(StrUtil.isNotEmpty(applyInfo.getCvTemplateJson())){
HotakeCvInfoDto cvInfoDto = handleAnalysisCvInfo(applyInfo.getCvTemplateJson());
applyInfo.setCvInfoDto(cvInfoDto);
}
if(applyInfo.getCandidateId() != null){
List<SysUser> userList = sysUserList.stream().filter(e->e.getUserId().longValue() == applyInfo.getCandidateId().longValue()).toList();
if(CollectionUtil.isNotEmpty(userList)) {
applyInfo.setCandidateUSer(userList.get(0));
}
}
if(StrUtil.isNotEmpty(applyInfo.getAiCvScoringRankingJson())){
HotakeAiCvScoringRankingDto rankingDto = JSONUtil.toBean(applyInfo.getAiCvScoringRankingJson(), HotakeAiCvScoringRankingDto.class);
applyInfo.setRankingDto(rankingDto);
}
}
}
return applyInfoList;
}
/**
* 招聘者查看候选人匹配度
* @param hotakeRolesApplyInfo 候选人岗位申请信息
* @return
*/
@Override
public HotakeRolesApplyInfo handleCandidateCompatibilityInfo(HotakeRolesApplyInfo hotakeRolesApplyInfo) {
if(CollectionUtil.isEmpty(hotakeRolesApplyInfo.getApplyRoleIdList())){
throw new ServiceException("Please select at least one piece of business data");
}
List<HotakeRolesApplyInfo> applyInfoList = hotakeRolesApplyInfoMapper.selectHotakeRolesApplyInfoCompatibilityScoreList(hotakeRolesApplyInfo);
if(CollectionUtil.isNotEmpty(applyInfoList)) {
HotakeRolesApplyInfo applyInfo = applyInfoList.get(0);
HotakeRolesInfo rolesInfo = hotakeRolesInfoMapper.selectHotakeRolesInfoById(applyInfo.getRoleId());
applyInfo.setRolesInfo(rolesInfo);
SysUser user = userService.selectUserById(applyInfo.getCandidateId());
applyInfo.setCandidateUSer(user);
if(StrUtil.isNotEmpty(applyInfo.getCandidateCompatibilityJson())){
HotakeCandidateCompatibilityDto compatibilityDto = JSONUtil.toBean(applyInfo.getCandidateCompatibilityJson(), HotakeCandidateCompatibilityDto.class);
applyInfo.setCompatibilityDto(compatibilityDto);
}
return applyInfo;
}
return new HotakeRolesApplyInfo();
}
}