简历解析业务逻辑处理

This commit is contained in:
2025-11-30 17:54:39 +08:00
parent 1bbd4294a8
commit 600ab3a248
17 changed files with 326 additions and 55 deletions

View File

@@ -1,5 +1,6 @@
package com.vetti.hotake.domain;
import com.vetti.hotake.domain.dto.HotakeCvInfoDto;
import lombok.Data;
import lombok.experimental.Accessors;
import io.swagger.annotations.ApiModelProperty;
@@ -52,4 +53,13 @@ public class HotakeCvInfo extends BaseEntity
@Excel(name = "状态", readConverterExp = "0=,禁=用1,启=用")
private String status;
@ApiModelProperty("简历详细信息-固定模版")
private HotakeCvInfoDto cvInfoDto;
@ApiModelProperty("简历预设问题记录")
private HotakeProblemBaseInfo problemBaseInfo;
@ApiModelProperty("简历评分")
private String score;
}

View File

@@ -32,6 +32,9 @@ public class HotakeProblemBaseInfo extends BaseEntity
@Excel(name = "问题")
private String contents;
@ApiModelProperty("问题数量")
private Integer questionNums;
/** 状态0 禁用1 启用) */
@ApiModelProperty("状态0 禁用1 启用)")
@Excel(name = "状态", readConverterExp = "0=,禁=用1,启=用")

View File

@@ -0,0 +1,40 @@
package com.vetti.hotake.domain.dto;
import com.vetti.hotake.domain.dto.VcDto.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 简历结构化信息对象 hotake_cv_info
*
* @author wangxiangshun
* @date 2025-11-30
*/
@Data
@Accessors(chain = true)
public class HotakeCvInfoDto {
@ApiModelProperty("姓名")
private String name;
@ApiModelProperty("电话")
private String phone;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("链接对象集合")
private List<VcLinksDto> linksList;
@ApiModelProperty("自我介绍")
private String about;
@ApiModelProperty("技能工具-许可证、注册/会员资格、认证")
private List<VcSkillsToolsDto> skillsToolsList;
@ApiModelProperty("语言")
private List<VcLanguagesDto> languagesList;
@ApiModelProperty("工作经验集合")
private List<VcExperienceDto> experienceList;
@ApiModelProperty("教育经历")
private List<VcEducationDto> educationList;
}

View File

@@ -0,0 +1,25 @@
package com.vetti.hotake.domain.dto.VcDto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 简历-教育经历-信息对象
*
* @author wangxiangshun
* @date 2025-11-30
*/
@Data
@Accessors(chain = true)
public class VcEducationDto {
@ApiModelProperty("学位")
private String degree;
@ApiModelProperty("学校名")
private String institution;
@ApiModelProperty("时间")
private String date;
}

View File

@@ -0,0 +1,19 @@
package com.vetti.hotake.domain.dto.VcDto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 简历-工作经验-描述-信息对象
*
* @author wangxiangshun
* @date 2025-11-30
*/
@Data
@Accessors(chain = true)
public class VcExperienceDescriptionDto {
@ApiModelProperty("内容")
private String content;
}

View File

@@ -0,0 +1,34 @@
package com.vetti.hotake.domain.dto.VcDto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 简历-工作经验-信息对象
*
* @author wangxiangshun
* @date 2025-11-30
*/
@Data
@Accessors(chain = true)
public class VcExperienceDto {
@ApiModelProperty("标题")
private String title;
@ApiModelProperty("公司")
private String company;
@ApiModelProperty("地点")
private String location;
@ApiModelProperty("时间")
private String duration;
@ApiModelProperty("描述集合")
private List<VcExperienceDescriptionDto> descriptionList;
}

View File

@@ -0,0 +1,19 @@
package com.vetti.hotake.domain.dto.VcDto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 简历-语言-信息对象
*
* @author wangxiangshun
* @date 2025-11-30
*/
@Data
@Accessors(chain = true)
public class VcLanguagesDto {
@ApiModelProperty("内容")
private String content;
}

View File

@@ -0,0 +1,19 @@
package com.vetti.hotake.domain.dto.VcDto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 简历链接-信息对象
*
* @author wangxiangshun
* @date 2025-11-30
*/
@Data
@Accessors(chain = true)
public class VcLinksDto {
@ApiModelProperty("内容")
private String content;
}

View File

@@ -0,0 +1,19 @@
package com.vetti.hotake.domain.dto.VcDto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 简历 技能工具-许可证、注册/会员资格、认证-信息对象
*
* @author wangxiangshun
* @date 2025-11-30
*/
@Data
@Accessors(chain = true)
public class VcSkillsToolsDto {
@ApiModelProperty("内容")
private String content;
}

View File

@@ -2,6 +2,8 @@ package com.vetti.hotake.service;
import java.util.List;
import com.vetti.hotake.domain.HotakeCvInfo;
import com.vetti.hotake.domain.HotakeProblemBaseInfo;
import com.vetti.hotake.domain.dto.HotakeCvInfoDto;
/**
* 简历信息Service接口
@@ -38,10 +40,10 @@ public interface IHotakeCvInfoService
/**
* 处理简历信息
*
* @param hotakeCvInfo 简历信息
* @param cvInfoDto 简历信息
* @return 结果
*/
public void handleHotakeCvInfo(HotakeCvInfo hotakeCvInfo);
public HotakeProblemBaseInfo handleHotakeCvInfo(HotakeCvInfoDto cvInfoDto);
/**

View File

@@ -1,13 +1,5 @@
package com.vetti.hotake.service.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
@@ -17,11 +9,12 @@ import com.vetti.common.enums.FillTypeEnum;
import com.vetti.common.enums.MinioBucketNameEnum;
import com.vetti.common.utils.DateUtils;
import com.vetti.common.utils.SecurityUtils;
import com.vetti.common.utils.file.MinioUtil;
import com.vetti.common.utils.readFile.FileContentUtil;
import com.vetti.common.utils.readText.ResumeTextExtractor;
import com.vetti.common.utils.readText.vo.ResumeData;
import com.vetti.hotake.domain.HotakeCvInfo;
import com.vetti.hotake.domain.HotakeProblemBaseInfo;
import com.vetti.hotake.domain.dto.HotakeCvInfoDto;
import com.vetti.hotake.mapper.HotakeCvInfoMapper;
import com.vetti.hotake.service.IHotakeCvInfoService;
import com.vetti.hotake.service.IHotakeProblemBaseInfoService;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
@@ -30,11 +23,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.vetti.hotake.mapper.HotakeCvInfoMapper;
import com.vetti.hotake.domain.HotakeCvInfo;
import com.vetti.hotake.service.IHotakeCvInfoService;
import javax.annotation.Resource;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* 简历信息Service业务层处理
@@ -113,8 +106,23 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
}
hotakeCvInfo.setCvFileSuffix(fileSuffix);
if("cv".equals(hotakeCvInfo.getCvFileType())){
//对简历数据进行处理生成相应的题库数据
handleHotakeCvInfo(hotakeCvInfo);
try{
InputStream inputStream = minioClient.getObject(
GetObjectArgs.builder()
.bucket(MinioBucketNameEnum.CV.getCode())
.object(hotakeCvInfo.getCvUrl())
.build());
String contents = FileContentUtil.readFileContent(inputStream,hotakeCvInfo.getCvFileSuffix());
//生成简历模版数据信息
HotakeCvInfoDto cvInfoDto = handleAnalysisCvInfo(contents);
hotakeCvInfo.setCvInfoDto(cvInfoDto);
//对简历数据进行处理生成相应的题库数据
HotakeProblemBaseInfo problemBaseInfo = handleHotakeCvInfo(cvInfoDto);
hotakeCvInfo.setProblemBaseInfo(problemBaseInfo);
//生成对应的简历评分
handleHotakeCvInfoScore(cvInfoDto);
}catch (Exception e){
}
}
return hotakeCvInfo;
}
@@ -125,19 +133,9 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
* @return
*/
@Override
public void handleHotakeCvInfo(HotakeCvInfo hotakeCvInfo) {
public HotakeProblemBaseInfo handleHotakeCvInfo(HotakeCvInfoDto cvInfoDto) {
HotakeProblemBaseInfo problemBaseInfo = new HotakeProblemBaseInfo();
try{
InputStream inputStream = minioClient.getObject(
GetObjectArgs.builder()
.bucket(MinioBucketNameEnum.CV.getCode())
.object(hotakeCvInfo.getCvUrl())
.build());
String contents = FileContentUtil.readFileContent(inputStream,hotakeCvInfo.getCvFileSuffix());
//进行简历数据提取
ResumeTextExtractor extractor = new ResumeTextExtractor();
ResumeData resumeData = extractor.extractResumeData(contents,"");
log.info("返回简历基本内容:{}", resumeData);
//调用AI大模型
List<Map<String,String>> list = new LinkedList();
Map<String,String> mapEntity = new HashMap<>();
@@ -147,35 +145,33 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
Map<String,String> mapEntityOne = new HashMap<>();
mapEntityOne.put("role","user");
mapEntityOne.put("content","Position: Construction Labourer"+
"\\nCandidate: "+resumeData.getPersonalInfo().getName()+
"\\nKey Skills: "+JSONUtil.toJsonStr(resumeData.getSkills())+
"\\nExperience: "+resumeData.getPersonalInfo().getExperienceYears()+"years"+
"\\nCandidate: "+cvInfoDto.getName()+
"\\nKey Skills: "+JSONUtil.toJsonStr(cvInfoDto.getSkillsToolsList())+
"\\nExperience: "+JSONUtil.toJsonStr(cvInfoDto.getExperienceList())+
"\\nStrengths: Professional certifications, Relevant experience"+
"\\nConcerns: "+JSONUtil.toJsonStr(resumeData.getSummary()));
"\\nConcerns: "+JSONUtil.toJsonStr(cvInfoDto.getAbout()));
list.add(mapEntityOne);
String promptText = JSONUtil.toJsonStr(list);
String resultMsg = chatGPTClient.handleAiChat(promptText,"CV");
log.info("返回简历问题数据:{}",resultMsg);
resultMsg = resultMsg.replaceAll("\n","#AA#");
String question = "";
Integer questionNums = 0;
if(StrUtil.isNotEmpty(resultMsg)){
String[] results = resultMsg.split("#AA#");
question = results[0].replaceAll(QUESTION_PREFIX,"");
if(StrUtil.isEmpty(question)){
//设置一个默认值
question = "Your professional certifications is impressive. Can you give me a specific example of how this benefited a project?";
}
resultMsg = resultMsg.replaceAll(QUESTION_PREFIX,"");
questionNums = resultMsg.split("#AA#").length;
}
log.info("返回初始化问题:{}",resultMsg);
//生成预设问题记录
//先删除该用户的预设问题
hotakeProblemBaseInfoService.deleteHotakeProblemBaseInfoByUserId(SecurityUtils.getUserId());
HotakeProblemBaseInfo problemBaseInfo = new HotakeProblemBaseInfo();
problemBaseInfo.setUserId(SecurityUtils.getUserId());
problemBaseInfo.setContents(question);
problemBaseInfo.setContents(resultMsg);
problemBaseInfo.setQuestionNums(questionNums);
hotakeProblemBaseInfoService.insertHotakeProblemBaseInfo(problemBaseInfo);
}catch (Exception e) {
e.printStackTrace();
}
return problemBaseInfo;
}
@@ -230,4 +226,63 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
public int batchInsertHotakeCvInfo(List<HotakeCvInfo> hotakeCvInfoList){
return hotakeCvInfoMapper.batchInsertHotakeCvInfo(hotakeCvInfoList);
}
/**
* 简历解析数据
* @param contents
* @return
*/
private HotakeCvInfoDto handleAnalysisCvInfo(String contents){
//根据AI做
String pom = "[\n" +
" {\n" +
" \"role\": \"user\",\n" +
" \"content\": \"从下面提供的文本中提取所有能识别到的简历信息只提取原文中存在的内容不要补充、不推测、不总结。需要提取的字段包括name姓名、phone电话号码、email电子邮件地址、links所有链接地址、about关于我/自我介绍、skills_tools关键资格许可证、注册/会员资格、认证、languages语言能力、experience工作经历(除了title、company、location、duration其他的都放到描述里面)、education教育经历。请将提取结果以结构化 JSON 格式返回,格式如下:{ \\\"name\\\": \\\"\\\", \\\"phone\\\": \\\"\\\", \\\"email\\\": \\\"\\\", \\\"links\\\": [], \\\"about\\\": \\\"\\\", \\\"skills_tools\\\": [], \\\"languages\\\": [], \\\"experience\\\": [{\\\"title\\\": \\\"\\\", \\\"company\\\": \\\"\\\",\\\"location\\\": \\\"\\\",\\\"duration\\\": \\\"\\\",\\\"description\\\": []}], \\\"education\\\": [{\\\"degree\\\": \\\"\\\",\\\"institution\\\": \\\"\\\",\\\"date\\\": \\\"\\\"}] }。字段不存在则返回 null 或空数组。只返回标准可解析的 JSON结构 ,不要多余的```json等信息不要解释说明不要改写内容。以下为待处理文本" + contents +
" }\n" +
"]";
String resultCv = chatGPTClient.handleAiChat(pom,"JX");
try{
HotakeCvInfoDto cvInfoDto = JSONUtil.toBean(resultCv,HotakeCvInfoDto.class);
return cvInfoDto;
}catch (Exception e){
}
return new HotakeCvInfoDto();
}
/**
* 处理简历信息
* @param hotakeCvInfo 简历信息
* @return
*/
private HotakeProblemBaseInfo handleHotakeCvInfoScore(HotakeCvInfoDto cvInfoDto) {
HotakeProblemBaseInfo problemBaseInfo = new HotakeProblemBaseInfo();
try{
//调用AI大模型
List<Map<String,String>> list = new LinkedList();
Map<String,String> mapEntity = new HashMap<>();
mapEntity.put("role","system");
mapEntity.put("content","You are a construction industry HR expert. Evaluate resumes and provide scores and recommendations for construction management positions.");
list.add(mapEntity);
Map<String,String> mapEntityOne = new HashMap<>();
mapEntityOne.put("role","user");
mapEntityOne.put("content","Position: Construction Labourer"+
"\\nCandidate: "+cvInfoDto.getName()+
"\\nResume Summary: "+JSONUtil.toJsonStr(cvInfoDto.getAbout())+
"\\nSkills: "+JSONUtil.toJsonStr(cvInfoDto.getSkillsToolsList())+
"\\nExperience: "+JSONUtil.toJsonStr(cvInfoDto.getExperienceList())+
"\\nEducation: "+JSONUtil.toJsonStr(cvInfoDto.getEducationList())+
"\\nCertifications: "+JSONUtil.toJsonStr(cvInfoDto.getSkillsToolsList()));
list.add(mapEntityOne);
String promptText = JSONUtil.toJsonStr(list);
String resultMsg = chatGPTClient.handleAiChat(promptText,"CV");
log.info("返回简历评分数据:{}",resultMsg);
}catch (Exception e) {
e.printStackTrace();
}
return problemBaseInfo;
}
}