From f38d157581a8134e11c4367ecc301bdef60dc378 Mon Sep 17 00:00:00 2001 From: wangxiangshun Date: Sat, 8 Nov 2025 11:52:59 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8E=86=E4=B8=9A=E5=8A=A1=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vetti/socket/ChatWebSocketHandler.java | 15 +-- .../src/main/resources/application.yml | 2 +- .../vetti/common/ai/gpt/ChatGPTClient.java | 2 +- .../common/enums/MinioBucketNameEnum.java | 30 ++++++ .../utils/readText/ResumeTextExtractor.java | 86 +++++++++++++++++ .../common/utils/readText/vo/ResumeData.java | 3 + .../handle/AuthenticationEntryPointImpl.java | 3 +- .../hotake/service/IHotakeCvInfoService.java | 2 +- .../service/impl/HotakeCvInfoServiceImpl.java | 96 +++++++++++-------- 9 files changed, 186 insertions(+), 53 deletions(-) create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/MinioBucketNameEnum.java diff --git a/vetti-admin/src/main/java/com/vetti/socket/ChatWebSocketHandler.java b/vetti-admin/src/main/java/com/vetti/socket/ChatWebSocketHandler.java index 5f48feb..73bffd2 100644 --- a/vetti-admin/src/main/java/com/vetti/socket/ChatWebSocketHandler.java +++ b/vetti-admin/src/main/java/com/vetti/socket/ChatWebSocketHandler.java @@ -37,11 +37,6 @@ import java.util.concurrent.ConcurrentHashMap; @Component public class ChatWebSocketHandler { - /** - * 追问问题标记 - */ - private final String QUESTION_FLAG = "FOLLOW-UP:"; - /** * 评分标记 */ @@ -180,16 +175,16 @@ public class ChatWebSocketHandler { Boolean isEndFlag = getInterviewScore(clientId,promptJson, session, ""); if(isEndFlag){ log.info("面试回答符合条件规则,继续追问啦!!!!!"); + final int[] resultNum = {(int) (Math.random() * 2) + 1}; aiStreamClient.streamChat(promptJson, new OpenAiStreamListenerService() { - int resultNum = (int) (Math.random() * 2) + 1; @Override public void onMessage(String content) { log.info("返回AI结果:{}", content.replaceAll("\n", "")); //获取1和2的随机数 - if(resultNum == 1){ + if(resultNum[0] == 1){ content = ""; } - resultNum = resultNum+1; + resultNum[0] = resultNum[0] +1; log.info("提问的问题:{}",content); // String contentData = content.replaceAll("\n", ""); //返回是追问的问题 @@ -441,7 +436,7 @@ public class ChatWebSocketHandler { resultEntity.put("type", "score"); try{ //返回评分语音 - sendTTSBuffer(clientId,resultMsg,session); +// sendTTSBuffer(clientId,resultMsg,session); //返回最终的评分结构 log.info("返回最终的评分结构:{}",JSONUtil.toJsonStr(resultEntity)); @@ -556,7 +551,7 @@ public class ChatWebSocketHandler { sendVoiceBuffer(openingPathUrl, session); //返回评分语音 - sendTTSBuffer(clientId,scoreText,session); +// sendTTSBuffer(clientId,scoreText,session); Map resultEntity = new HashMap<>(); resultEntity.put("content", scoreText); diff --git a/vetti-admin/src/main/resources/application.yml b/vetti-admin/src/main/resources/application.yml index 971effa..2eb1d0e 100644 --- a/vetti-admin/src/main/resources/application.yml +++ b/vetti-admin/src/main/resources/application.yml @@ -44,7 +44,7 @@ token: # 令牌密钥 secret: abcdefghijklmnopqrstuvwxyz # 令牌有效期(默认30分钟) - expireTime: 30 + expireTime: 2400 # MyBatis配置 mybatis: diff --git a/vetti-common/src/main/java/com/vetti/common/ai/gpt/ChatGPTClient.java b/vetti-common/src/main/java/com/vetti/common/ai/gpt/ChatGPTClient.java index 521a99f..cb48c30 100644 --- a/vetti-common/src/main/java/com/vetti/common/ai/gpt/ChatGPTClient.java +++ b/vetti-common/src/main/java/com/vetti/common/ai/gpt/ChatGPTClient.java @@ -60,7 +60,7 @@ public class ChatGPTClient { if("CV".equals(type)){ resultText = sendMessage(promptText, modelCV,objectMapper,client,role); }else if("QA".equals(type)){ - resultText = sendMessage(promptText, "ft:gpt-3.5-turbo-0125:vetti:construction-labourer-test:CWKBNvE2",objectMapper,client,role); + resultText = sendMessage(promptText, model,objectMapper,client,role); } else { resultText = sendMessage(promptText, model,objectMapper,client,role); } diff --git a/vetti-common/src/main/java/com/vetti/common/enums/MinioBucketNameEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/MinioBucketNameEnum.java new file mode 100644 index 0000000..1c2b31d --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/MinioBucketNameEnum.java @@ -0,0 +1,30 @@ +package com.vetti.common.enums; + +/** + * minio 桶的名称 + */ +public enum MinioBucketNameEnum { + + CV("cv-fs", "简历"), + NOTICE_TYPE("noticetype-fs", "通知"), + ; + + private final String code; + private final String info; + + MinioBucketNameEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/readText/ResumeTextExtractor.java b/vetti-common/src/main/java/com/vetti/common/utils/readText/ResumeTextExtractor.java index 39c7750..e87ae09 100644 --- a/vetti-common/src/main/java/com/vetti/common/utils/readText/ResumeTextExtractor.java +++ b/vetti-common/src/main/java/com/vetti/common/utils/readText/ResumeTextExtractor.java @@ -563,6 +563,90 @@ public class ResumeTextExtractor { return projects.getOrDefault(role, projects.get("Project Manager")); } + + /** + * 提取个人总结/简介 + * + * @param text 简历文本 + * @param role 申请职位 + * @param experienceYears 工作经验年数 + * @param skills 技能列表 + * @return 个人总结 + */ + public String extractSummary(String text, String role, int experienceYears, List skills) { + // 查找明确的总结段落 + String[] summaryPatterns = { + "(?:summary|profile|objective|about|overview)[:\\s]*([^\\n\\r]{50,200})", + "(?:professional\\s+summary|career\\s+summary|personal\\s+statement)[:\\s]*([^\\n\\r]{50,200})" + }; + + for (String patternStr : summaryPatterns) { + Pattern pattern = Pattern.compile(patternStr, Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(text); + + if (matcher.find() && matcher.group(1) != null) { + String summary = matcher.group(1).trim(); + if (summary.length() >= 30 && summary.length() <= 200) { + return cleanSummary(summary); + } + } + } + + // 如果没有找到明确的总结,基于经验和技能生成 + return generateSummary(role, experienceYears, skills); + } + + /** + * 清理总结文本 + * + * @param summary 原始总结文本 + * @return 清理后的总结 + */ + private String cleanSummary(String summary) { + return summary + .replaceAll("[^\\w\\s\\-\\.,]", "") // 移除特殊字符 + .replaceAll("\\s+", " ") // 合并多个空格 + .trim(); + } + + /** + * 基于职位和经验生成个人总结 + * + * @param role 申请职位 + * @param experienceYears 工作经验年数 + * @param skills 技能列表 + * @return 生成的个人总结 + */ + private String generateSummary(String role, int experienceYears, List skills) { + String experienceLevel = experienceYears >= 8 ? "Senior" : + experienceYears >= 4 ? "Experienced" : "Entry-level"; + + String roleDesc; + if ("Project Manager".equals(role)) { + roleDesc = "construction professional with proven project management experience"; + } else { + roleDesc = "contracts professional with strong legal and negotiation background"; + } + + String topSkills = ""; + if (skills.size() >= 2) { + topSkills = skills.get(0).toLowerCase() + " and " + skills.get(1).toLowerCase(); + } else if (!skills.isEmpty()) { + topSkills = skills.get(0).toLowerCase(); + } + + if (experienceYears >= 8) { + return String.format("%s %s with %d years of experience in %s and industry best practices", + experienceLevel, roleDesc, experienceYears, topSkills); + } else if (experienceYears >= 4) { + return String.format("%s %s with solid background in %s and commitment to quality delivery", + experienceLevel, roleDesc, topSkills); + } else { + return String.format("%s candidate seeking career growth in construction with foundation in %s", + experienceLevel, topSkills); + } + } + /** * 主要提取方法 - 从简历文本中提取所有结构化信息 * @@ -591,6 +675,8 @@ public class ResumeTextExtractor { // 教育背景列表 resumeData.setEducation(extractEducation(text)); + resumeData.setSummary(extractSummary(text, role, personalInfo.getExperienceYears(), resumeData.getSkills())); + return resumeData; } diff --git a/vetti-common/src/main/java/com/vetti/common/utils/readText/vo/ResumeData.java b/vetti-common/src/main/java/com/vetti/common/utils/readText/vo/ResumeData.java index 9023e34..2efbccb 100644 --- a/vetti-common/src/main/java/com/vetti/common/utils/readText/vo/ResumeData.java +++ b/vetti-common/src/main/java/com/vetti/common/utils/readText/vo/ResumeData.java @@ -27,4 +27,7 @@ public class ResumeData { @ApiModelProperty("候选人教育经历") private List education; + + @ApiModelProperty("个人总结") + private String summary; } diff --git a/vetti-framework/src/main/java/com/vetti/framework/security/handle/AuthenticationEntryPointImpl.java b/vetti-framework/src/main/java/com/vetti/framework/security/handle/AuthenticationEntryPointImpl.java index 341b496..23357b6 100644 --- a/vetti-framework/src/main/java/com/vetti/framework/security/handle/AuthenticationEntryPointImpl.java +++ b/vetti-framework/src/main/java/com/vetti/framework/security/handle/AuthenticationEntryPointImpl.java @@ -28,7 +28,8 @@ public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, S throws IOException { int code = HttpStatus.UNAUTHORIZED; - String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); + //认证失败,无法访问系统资源 + String msg = StringUtils.format("Request access:{},authentication failed, unable to access system resources", request.getRequestURI()); ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg))); } } diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeCvInfoService.java b/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeCvInfoService.java index 764b40d..f3baeca 100644 --- a/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeCvInfoService.java +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeCvInfoService.java @@ -41,7 +41,7 @@ public interface IHotakeCvInfoService * @param hotakeCvInfo 简历信息 * @return 结果 */ - public HotakeCvInfo handleHotakeCvInfo(HotakeCvInfo hotakeCvInfo); + public void handleHotakeCvInfo(HotakeCvInfo hotakeCvInfo); /** diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/service/impl/HotakeCvInfoServiceImpl.java b/vetti-hotakes/src/main/java/com/vetti/hotake/service/impl/HotakeCvInfoServiceImpl.java index aef84a4..1bdd55e 100644 --- a/vetti-hotakes/src/main/java/com/vetti/hotake/service/impl/HotakeCvInfoServiceImpl.java +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/service/impl/HotakeCvInfoServiceImpl.java @@ -8,17 +8,22 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.vetti.common.ai.gpt.ChatGPTClient; import com.vetti.common.core.service.BaseServiceImpl; 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.HotakeProblemBaseInfo; import com.vetti.hotake.service.IHotakeProblemBaseInfoService; +import io.minio.GetObjectArgs; +import io.minio.MinioClient; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -28,6 +33,8 @@ import com.vetti.hotake.mapper.HotakeCvInfoMapper; import com.vetti.hotake.domain.HotakeCvInfo; import com.vetti.hotake.service.IHotakeCvInfoService; +import javax.annotation.Resource; + /** * 简历信息Service业务层处理 * @@ -39,12 +46,22 @@ import com.vetti.hotake.service.IHotakeCvInfoService; @Service public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeCvInfoService { + + /** + * 问题前缀 + */ + private final String QUESTION_PREFIX = "Question:"; + + @Autowired private HotakeCvInfoMapper hotakeCvInfoMapper; @Autowired private ChatGPTClient chatGPTClient; + @Autowired + private MinioClient minioClient; + @Autowired private IHotakeProblemBaseInfoService hotakeProblemBaseInfoService; @@ -99,55 +116,56 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC * @return */ @Override - public HotakeCvInfo handleHotakeCvInfo(HotakeCvInfo hotakeCvInfo) { + public void handleHotakeCvInfo(HotakeCvInfo hotakeCvInfo) { try{ -// InputStream inputStream = new FileInputStream("/Users/wangxiangshun/Desktop/管报数据/223/Abrar Mohammed Project Manager Resume.docx"); -// String contents = FileContentUtil.readFileContent(inputStream,hotakeCvInfo.getCvFileType()); -// //进行简历数据提取 -// ResumeTextExtractor extractor = new ResumeTextExtractor(); -// ResumeData resumeData = extractor.extractResumeData(contents,""); -// log.info("返回简历基本内容:{}", resumeData); - //根据简历信息生成初始化问题集合 -// messages: [ -// { -// role: "system", -// content: "You are a construction industry HR expert. Evaluate resumes and provide a score (1-5) and brief recommendation for construction positions." -// }, -// { -// role: "user", -// content: `Position: ${resume.position}\nCandidate: ${resume.candidate}\nExperience: ${resume.experience}\nSkills: ${resume.skills}\nEducation: ${resume.education}\nCertifications: ${resume.certifications}\nSummary: ${resume.summary}` -// } -// ] + InputStream inputStream = minioClient.getObject( + GetObjectArgs.builder() + .bucket(MinioBucketNameEnum.CV.getCode()) + .object(hotakeCvInfo.getCvUrl()) + .build()); + String contents = FileContentUtil.readFileContent(inputStream,hotakeCvInfo.getCvFileType()); + //进行简历数据提取 + ResumeTextExtractor extractor = new ResumeTextExtractor(); + ResumeData resumeData = extractor.extractResumeData(contents,""); + log.info("返回简历基本内容:{}", resumeData); //调用AI大模型 -// List> list = new LinkedList(); -// Map mapEntity = new HashMap<>(); -// mapEntity.put("role","system"); -// mapEntity.put("content","You are a construction industry HR expert and experienced interviewer. Evaluate the resume and generate interview questions in one response. Use this exact format:\\n\\nEVALUATION:\\nScore: X/5\\nRecommendation: [recommendation]\\nStrengths: [strengths]\\nConcerns: [concerns]\\n\\nINTERVIEW QUESTIONS:\\nQuestion 1: [question]\\nCategory: [category]\\nReasoning: [reasoning]\\n\\nQuestion 2: [question]\\nCategory: [category]\\nReasoning: [reasoning]"); -// list.add(mapEntity); -// Map mapEntityOne = new HashMap<>(); -// mapEntityOne.put("role","user"); -// mapEntityOne.put("content","Position: Construction Labourer"+ -// "\\nCandidate: "+resumeData.getPersonalInfo().getName()+ -// "\\nExperience: "+resumeData.getPersonalInfo().getExperienceYears()+ -// "\\nSkills: "+JSONUtil.toJsonStr(resumeData.getSkills())+ -// "\\nEducation: "+JSONUtil.toJsonStr(resumeData.getEducation())+ -// "\\nCertifications: "+JSONUtil.toJsonStr(resumeData.getPersonalInfo().getCertifications())+ -// "\\nSummary: Experienced construction worker with strong safety record"); -// list.add(mapEntityOne); -// String promptText = JSONUtil.toJsonStr(list); -// String resultMsg = chatGPTClient.handleAiChat(promptText,"CV"); -// log.info("返回初始化问题:{}",resultMsg); + List> list = new LinkedList(); + Map mapEntity = new HashMap<>(); + mapEntity.put("role","system"); + mapEntity.put("content","You are an experienced interviewer for construction management positions. Generate targeted interview questions based on candidate resumes."); + list.add(mapEntity); + Map 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"+ + "\\nStrengths: Professional certifications, Relevant experience"+ + "\\nConcerns: "+JSONUtil.toJsonStr(resumeData.getSummary())); + list.add(mapEntityOne); + String promptText = JSONUtil.toJsonStr(list); + String resultMsg = chatGPTClient.handleAiChat(promptText,"CV"); + resultMsg = resultMsg.replaceAll("\n","#AA#"); + String question = ""; + 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?"; + } + } + log.info("返回初始化问题:{}",resultMsg); //生成预设问题记录 //先删除该用户的预设问题 hotakeProblemBaseInfoService.deleteHotakeProblemBaseInfoByUserId(SecurityUtils.getUserId()); HotakeProblemBaseInfo problemBaseInfo = new HotakeProblemBaseInfo(); problemBaseInfo.setUserId(SecurityUtils.getUserId()); - problemBaseInfo.setContents("Can you provide an example of a situation where you had to work in a physically demanding environment?,How do you plan to handle tasks that require specific construction knowledge or skills that you may not have yet?"); + problemBaseInfo.setContents(question); hotakeProblemBaseInfoService.insertHotakeProblemBaseInfo(problemBaseInfo); }catch (Exception e) { - + e.printStackTrace(); } - return null; }