业务逻辑修改以及完善

This commit is contained in:
2026-01-11 23:31:20 +08:00
parent 6edf7a4958
commit 85294a917a
17 changed files with 1014 additions and 378 deletions

View File

@@ -366,6 +366,13 @@
<version>1.0.10</version> <version>1.0.10</version>
</dependency> </dependency>
<!-- WebSocket Client -->
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
<version>2.1.3</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@@ -91,6 +91,12 @@
<artifactId>concentus</artifactId> <artifactId>concentus</artifactId>
</dependency> </dependency>
<!-- WebSocket Client -->
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
</dependency>
</dependencies> </dependencies>

View File

@@ -1,173 +1,80 @@
package com.vetti.socket.agents; package com.vetti.socket.agents;
import okhttp3.*; import lombok.extern.slf4j.Slf4j;
import okio.ByteString;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.ContainerProvider;
import javax.websocket.WebSocketContainer;
import java.net.URI;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/** @Slf4j
* public class ElevenLabsAgentClient {
*/
public class ElevenLabsAgentClient extends TextWebSocketHandler{
// 存储Vue会话与ElevenLabs WebSocket的映射多客户端隔离 private static final String AGENT_WS_URL =
private static final Map<WebSocketSession, WebSocket> SESSION_MAP = new ConcurrentHashMap<>(); "wss://api.elevenlabs.io/v1/agents/%s/stream";
// ElevenLabs配置
private static final String ELEVEN_LABS_API_KEY = "你的ElevenLabs API Key";
private static final String AGENT_ID = "你的ElevenLabs Agent ID";
private static final String ELEVEN_LABS_WSS_URL = "wss://api.elevenlabs.io/v1/agents/" + AGENT_ID + "/stream"; private final ElevenLabsAgentEndpoint endpoint;
// OkHttp客户端
private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient.Builder() public ElevenLabsAgentClient(String traceId, WebSocketSession frontendSession) {
.readTimeout(0, TimeUnit.MILLISECONDS) // WebSocket长连接取消读超时 this.endpoint = new ElevenLabsAgentEndpoint();
connect(traceId, frontendSession);
}
private void connect(String traceId, WebSocketSession frontendSession) {
try {
log.info("[traceId={}] Connecting to ElevenLabs Agent...", traceId);
WebSocketContainer container =
ContainerProvider.getWebSocketContainer();
ClientEndpointConfig config =
ClientEndpointConfig.Builder.create()
.configurator(new ClientEndpointConfig.Configurator() {
@Override
public void beforeRequest(
Map<String, List<String>> headers
) {
headers.put(
"xi-api-key",
List.of("sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116")
);
}
})
.build(); .build();
// ==================== 1. 处理Vue前端连接 ==================== config.getUserProperties().put("traceId", traceId);
@Override config.getUserProperties().put("frontendSession", frontendSession);
public void afterConnectionEstablished(WebSocketSession vueSession) throws Exception {
super.afterConnectionEstablished(vueSession);
System.out.println("Vue前端连接成功" + vueSession.getId());
// 建立与ElevenLabs Agents的WSS连接 container.connectToServer(
buildElevenLabsWssConnection(vueSession); endpoint,
} config,
URI.create(
String.format(
AGENT_WS_URL,
"9c5cb2f7ba9efb61d0f0eee01427b6e00c6abe92d4754cfb794884ac4d73c79d"
)
)
);
// ==================== 2. 处理Vue前端发送的文本消息如语音指令、配置信息 ====================
@Override
protected void handleTextMessage(WebSocketSession vueSession, TextMessage message) throws Exception {
super.handleTextMessage(vueSession, message);
System.out.println("接收Vue文本消息" + message.getPayload());
// 获取对应ElevenLabs WebSocket连接转发消息
WebSocket elevenLabsWs = SESSION_MAP.get(vueSession);
if (elevenLabsWs != null && elevenLabsWs.queueSize() == 0) {
elevenLabsWs.send(message.getPayload());
System.out.println("转发文本消息到ElevenLabs成功");
}
}
// ==================== 3. 处理Vue前端发送的二进制消息核心语音流数据 ====================
@Override
protected void handleBinaryMessage(WebSocketSession vueSession, BinaryMessage message) {
super.handleBinaryMessage(vueSession, message);
byte[] voiceData = message.getPayload().array();
System.out.println("接收Vue语音流数据字节长度" + voiceData.length);
// 获取对应ElevenLabs WebSocket连接转发语音流二进制
WebSocket elevenLabsWs = SESSION_MAP.get(vueSession);
if (elevenLabsWs != null && elevenLabsWs.queueSize() == 0) {
elevenLabsWs.send(ByteString.of(voiceData));
System.out.println("转发语音流到ElevenLabs成功");
}
// 释放二进制消息资源
// message.isLast();
}
// ==================== 4. 处理Vue前端连接关闭 ====================
@Override
public void afterConnectionClosed(WebSocketSession vueSession, org.springframework.web.socket.CloseStatus status) throws Exception {
super.afterConnectionClosed(vueSession, status);
System.out.println("Vue前端连接关闭" + vueSession.getId() + ",原因:" + status.getReason());
// 关闭对应的ElevenLabs WSS连接
WebSocket elevenLabsWs = SESSION_MAP.remove(vueSession);
if (elevenLabsWs != null) {
elevenLabsWs.close(1000, "Vue客户端断开连接");
System.out.println("关闭ElevenLabs WSS连接成功");
}
}
// ==================== 5. 建立与ElevenLabs Agents的WSS连接 ====================
private void buildElevenLabsWssConnection(WebSocketSession vueSession) {
// 1. 构建ElevenLabs WSS请求携带认证头
Request request = new Request.Builder()
.url(ELEVEN_LABS_WSS_URL)
.header("xi-api-key", ELEVEN_LABS_API_KEY) // 必选认证头
.build();
// 2. 构建ElevenLabs WSS监听器处理响应并回流到Vue
WebSocketListener elevenLabsListener = new WebSocketListener() {
// ElevenLabs WSS连接建立
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
System.out.println("与ElevenLabs Agents WSS连接成功");
// 存储Vue会话与ElevenLabs WS的映射
SESSION_MAP.put(vueSession, webSocket);
}
// 接收ElevenLabs文本消息如状态、错误提示回流到Vue
@Override
public void onMessage(WebSocket webSocket, String text) {
super.onMessage(webSocket, text);
System.out.println("接收ElevenLabs文本消息" + text);
try {
// 回流到Vue前端
if (vueSession.isOpen()) {
vueSession.sendMessage(new TextMessage(text));
}
} catch (Exception e) { } catch (Exception e) {
System.err.println("回流文本消息到Vue失败" + e.getMessage()); throw new RuntimeException("Connect ElevenLabs failed", e);
} }
} }
// 接收ElevenLabs二进制消息核心音频流响应回流到Vue public void sendAudio(java.nio.ByteBuffer buffer) {
@Override endpoint.sendAudio(buffer);
public void onMessage(WebSocket webSocket, ByteString bytes) {
super.onMessage(webSocket, bytes);
byte[] audioData = bytes.toByteArray();
System.out.println("接收ElevenLabs音频流数据字节长度" + audioData.length);
try {
// 回流二进制音频流到Vue前端
if (vueSession.isOpen()) {
vueSession.sendMessage(new BinaryMessage(audioData));
}
} catch (Exception e) {
System.err.println("回流音频流到Vue失败" + e.getMessage());
}
} }
// ElevenLabs WSS连接关闭 public void sendText(String text) {
@Override endpoint.sendText(text);
public void onClosed(WebSocket webSocket, int code, String reason) {
super.onClosed(webSocket, code, reason);
System.out.println("ElevenLabs WSS连接关闭" + reason + ",状态码:" + code);
// 移除映射关闭Vue连接
SESSION_MAP.remove(vueSession);
try {
if (vueSession.isOpen()) {
vueSession.close(org.springframework.web.socket.CloseStatus.NORMAL);
}
} catch (Exception e) {
e.printStackTrace();
}
} }
// ElevenLabs WSS连接异常 public void close() {
@Override endpoint.close();
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
super.onFailure(webSocket, t, response);
System.err.println("ElevenLabs WSS连接异常" + t.getMessage());
// 移除映射关闭Vue连接
SESSION_MAP.remove(vueSession);
try {
if (vueSession.isOpen()) {
vueSession.close(org.springframework.web.socket.CloseStatus.SERVER_ERROR);
}
} catch (Exception e) {
e.printStackTrace();
} }
} }
};
// 3. 建立ElevenLabs WSS连接
OK_HTTP_CLIENT.newWebSocket(request, elevenLabsListener);
}
}

View File

@@ -0,0 +1,88 @@
package com.vetti.socket.agents;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import javax.websocket.*;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class ElevenLabsAgentEndpoint extends Endpoint {
private Session agentSession;
private String traceId;
private WebSocketSession frontendSession;
private final AtomicInteger audioCount = new AtomicInteger();
@Override
public void onOpen(Session session, EndpointConfig config) {
log.info("客户端链接啦:{}", traceId);
this.agentSession = session;
this.traceId = (String) config.getUserProperties().get("traceId");
this.frontendSession =
(WebSocketSession) config.getUserProperties().get("frontendSession");
log.info("[traceId={}] ElevenLabs Agent CONNECTED", traceId);
session.addMessageHandler(String.class, this::onText);
session.addMessageHandler(ByteBuffer.class, this::onAudio);
}
private void onText(String message) {
try {
log.info("[traceId={}] Agent → TEXT {}", traceId, message);
frontendSession.sendMessage(new TextMessage(message));
} catch (IOException e) {
log.error("[traceId={}] Send text failed", traceId, e);
}
}
private void onAudio(ByteBuffer buffer) {
try {
int count = audioCount.incrementAndGet();
if (count == 1) {
log.info(
"[traceId={}] Agent → AUDIO FIRST packet size={} bytes",
traceId,
buffer.remaining()
);
}
frontendSession.sendMessage(new BinaryMessage(buffer));
} catch (IOException e) {
log.error("[traceId={}] Send audio failed", traceId, e);
}
}
@Override
public void onClose(Session session, CloseReason closeReason) {
log.info("[traceId={}] ElevenLabs Agent CLOSED {}", traceId, closeReason);
}
public void sendAudio(ByteBuffer buffer) {
if (agentSession != null && agentSession.isOpen()) {
agentSession.getAsyncRemote().sendBinary(buffer);
}
}
public void sendText(String text) {
if (agentSession != null && agentSession.isOpen()) {
agentSession.getAsyncRemote().sendText(text);
}
}
public void close() {
try {
if (agentSession != null) {
agentSession.close();
}
} catch (Exception ignored) {}
}
}

View File

@@ -0,0 +1,17 @@
package com.vetti.socket.agents;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Data
public class ElevenLabsConfig {
@Value("${elevenLabs.agent-id}")
private String agentId;
@Value("${elevenLabs.api-key}")
private String apiKey;
}

View File

@@ -0,0 +1,36 @@
package com.vetti.socket.agents;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.BinaryWebSocketHandler;
@Slf4j
public class FrontendWebSocketHandler extends BinaryWebSocketHandler {
private ElevenLabsAgentClient agentClient;
@Override
public void afterConnectionEstablished(WebSocketSession session) {
String traceId = session.getId();
log.info("[traceId={}] Vue WebSocket CONNECTED", traceId);
agentClient = new ElevenLabsAgentClient(traceId, session);
}
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
agentClient.sendAudio(message.getPayload());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
agentClient.sendText(message.getPayload());
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
log.info("[traceId={}] Vue WebSocket CLOSED", session.getId());
agentClient.close();
}
}

View File

@@ -0,0 +1,16 @@
package com.vetti.socket.agents;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.*;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new FrontendWebSocketHandler(), "/voice-websocket/elevenLabsAgent/{clientId}")
.setAllowedOrigins("*");
}
}

View File

@@ -3,6 +3,7 @@ package com.vetti.web.controller.ai;
import com.vetti.common.core.controller.BaseController; import com.vetti.common.core.controller.BaseController;
import com.vetti.common.core.domain.R; import com.vetti.common.core.domain.R;
import com.vetti.hotake.domain.HotakeInitialScreeningQuestionsInfo; import com.vetti.hotake.domain.HotakeInitialScreeningQuestionsInfo;
import com.vetti.hotake.domain.HotakeRolesInfo;
import com.vetti.hotake.domain.dto.*; import com.vetti.hotake.domain.dto.*;
import com.vetti.hotake.domain.vo.*; import com.vetti.hotake.domain.vo.*;
import com.vetti.hotake.service.IHotakeAiCommonToolsService; import com.vetti.hotake.service.IHotakeAiCommonToolsService;
@@ -160,4 +161,24 @@ public class HotakeAiCommonToolsController extends BaseController {
return R.ok(hotakeAiCommonToolsService.getRoleLinkAnalysis(roleLinkAnalysisVo)); return R.ok(hotakeAiCommonToolsService.getRoleLinkAnalysis(roleLinkAnalysisVo));
} }
/**
* 招聘信息分析补全
*/
@ApiOperation("招聘信息分析补全(按照步骤生成)")
@PostMapping(value = "/roleInfoAnalysisSetUp")
public R<HotakeRolesInfoDto> handleRoleInfoAnalysisSetUp(@RequestBody HotakeRolesInfoDto rolesInfoDto)
{
return R.ok(hotakeAiCommonToolsService.getRoleInfoAnalysisSetUp(rolesInfoDto));
}
/**
* AI面试问题生成
*/
@ApiOperation("AI面试问题生成")
@PostMapping(value = "/aiInterviewQuestions")
public R<?> handleAiInterviewQuestions(@RequestBody HotakeRolesInfo rolesInfo)
{
return R.ok(hotakeAiCommonToolsService.getAiInterviewQuestions(rolesInfo));
}
} }

View File

@@ -155,6 +155,8 @@ elevenLabs:
apiKey: sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116 apiKey: sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116
# apiKey: sk_88f5a560e1bbde0e5b8b6b6eb1812163a98bfb98554acbec # apiKey: sk_88f5a560e1bbde0e5b8b6b6eb1812163a98bfb98554acbec
modelId: eleven_turbo_v2_5 modelId: eleven_turbo_v2_5
agent-id: 9c5cb2f7ba9efb61d0f0eee01427b6e00c6abe92d4754cfb794884ac4d73c79d
api-key: sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116
# 语音转文本 # 语音转文本
whisper: whisper:

View File

@@ -155,6 +155,8 @@ elevenLabs:
apiKey: sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116 apiKey: sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116
# apiKey: sk_88f5a560e1bbde0e5b8b6b6eb1812163a98bfb98554acbec # apiKey: sk_88f5a560e1bbde0e5b8b6b6eb1812163a98bfb98554acbec
modelId: eleven_turbo_v2_5 modelId: eleven_turbo_v2_5
agent-id: 9c5cb2f7ba9efb61d0f0eee01427b6e00c6abe92d4754cfb794884ac4d73c79d
api-key: sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116
# 语音转文本 # 语音转文本
whisper: whisper:

View File

@@ -1701,95 +1701,105 @@ public class AiCommonPromptConstants {
* @return * @return
*/ */
public static String initializationRoleLinkAnalysisPrompt(){ public static String initializationRoleLinkAnalysisPrompt(){
String promptStr = "你是一位专业的招聘信息补全专家专门根据已提取的岗位信息进行AI智能补全严格遵守业务约束规则生成标准API格式响应。\n" + String promptStr = "You are a professional job posting information completion expert, specializing in AI intelligent completion based on extracted job information, strictly following business constraint rules to generate standard API format responses.\n" +
"\n" + "\n" +
"## 核心任务\n" + "## Core Task\n" +
"根据用户提供的「已提取岗位信息」生成完整的、符合业务约束规则的标准API格式岗位数据。\n" + "Generate complete job data that complies with business constraint rules in standard API format based on the \"extracted job information\" provided by the user.\n" +
"\n" + "\n" +
"## 输入信息说明\n" + "## Input Information Description\n" +
"用户将提供一个JSON对象包含从招聘页面提取的基础信息其中\n" + "The user will provide a JSON object containing basic information extracted from job posting pages, where:\n" +
"- **有值的字段**:表示从页面准确提取的信息,请保留原值\n" + "- **Fields with values**: Represent information accurately extracted from the page, please retain original values\n" +
"- **null值字段**表示页面未找到的信息需要AI智能补全\n" + "- **null value fields**: Represent information not found on the page, requiring AI intelligent completion\n" +
"- **非标准格式字段**:需要转换为业务规则要求的标准格式\n" + "- **Non-standard format fields**: Need to be converted to standard format required by business rules\n" +
"\n" + "\n" +
"## 信息处理原则\n" + "## Information Processing Principles\n" +
"1. **保留准确信息**:对于已从页面准确提取的信息(如jobTitlecompanyNamelocation),请直接保留原值\n" + "1. **Preserve Accurate Information**: For information accurately extracted from the page (such as jobTitle, companyName, location), please retain the original values directly\n" +
"2. **格式标准化**:对于格式不标准的信息,请转换为业务规则要求的标准格式\n" + "2. **Format Standardization**: For non-standard format information, please convert to standard format required by business rules\n" +
" - `experience: \"3-5年工作经验\"` → `jobExperience: \"2-3 Years\"`\n" + " - `experience: \"3-5 years experience\"` → `jobExperience: 4`\n" +
" - `education: \"本科及以上学历\"` → 标准教育要求格式\n" + " - `education: \"Bachelor's degree or above\"` → Standard education requirements format\n" +
" - `skills: [\"Java\", \"Spring\"]` → 规则允许的技能格式\n" + " - `skills: [\"Java\", \"Spring\"]` → Skill format allowed by rules\n" +
" - `salaryRange: \"15K-25K\"` → `salaryStart: 90000, salaryEnd: 150000`\n" + " - `salaryRange: \"15K-25K\"` → `salaryStart: 90000, salaryEnd: 150000`\n" +
"3. **智能补全**对于标记为null的缺失信息请根据岗位特点和行业标准进行合理补全\n" + "3. **Intelligent Completion**: For missing information marked as null, please reasonably complete based on job characteristics and industry standards\n" +
"4. **严格合规**:所有字段必须遵守业务约束规则表,禁止生成规则外的值\n" + "4. **Strict Compliance**: All fields must comply with business constraint rules, and values outside the rules are prohibited\n" +
"\n" + "\n" +
"## 业务约束规则表\n" + "## Business Constraint Rules Table\n" +
"| 字段名 | 约束类型 | 业务约束值/规则 |\n" + "| Field Name | Constraint Type | Business Constraint Values/Rules |\n" +
"|----------------------------|----------------|--------------------------------------------------------------------------------|\n" + "|----------------------------|-------------------|--------------------------------------------------------------------------------|\n" +
"| jobType | 可选值集合 | 仅允许:Full-time/Part-time/Contract/Temporary/Internship |\n" + "| jobType | Optional Value Set | Only allow: Full-time/Part-time/Contract/Temporary/Internship |\n" +
"| jobLevel | 可选值集合 | 仅允许:Entry/Junior/Mid/Senior/Lead/Manager/Director |\n" + "| jobLevel | Optional Value Set | Only allow: Entry/Junior/Mid/Senior/Lead/Manager/Director |\n" +
"| jobExperience | 文本输入 | 自由文本描述工作经验要求,如\"3-5年相关工作经验\"\"2年以上Java开发经验\" |\n" + "| jobExperience | Integer | Work experience years required, integer value 1-10, e.g., 1 for 1 year, 5 for 5 years, 10 for 10+ years |\n" +
"| locationType | 可选值集合 | 仅允许On-site/Remote/Hybrid |\n" +
"| status | 可选值集合 | 仅允许pause/archived/open/editing |\n" +
"| dataType | 可选值集合 | 仅允许release/draft/template |\n" +
"| salaryStart/salaryEnd | 数值范围+类型 | 类型数字BigDecimal范围salaryStart≥50000salaryEnd≤200000且salaryEnd≥salaryStart |\n" +
"| descriptionTone | 可选值集合 | 仅允许professional/friendly/innovative/dynamic四选一数组格式 |\n" +
"| aboutRole | 文本输入 | 关于职位的详细描述,包含岗位背景、重要性、发展前景等 |\n" +
"| responsibilities | 文本输入 | 详细的岗位职责描述,包含主要工作内容和具体任务 |\n" +
"| roleBenefitsList | 结构+取值规则 | 1. 结构:数组,每个元素仅含 keyValue 字段;<br>2. 取值:自由文本,如\"五险一金\"\"年终奖金\"\"带薪年假\"等;<br>3. 数量3-8项 |\n" +
"| requiredSkillsList | 结构+取值规则 | 1. 结构:数组,每个元素仅含 keyValue 字段;<br>2. 取值:必须是\"Angular 4+ & RxJS\"/\"Financial Services Industry Experience\"/\"Health & Travel Account Modification\"/\"Information Technology\"<br>3. 数量1-5项 |\n" +
"| niceToHaveSkillsList | 结构+取值规则 | 1. 结构:数组,每个元素仅含 keyValue 字段;<br>2. 取值:必须是\"Financial Services Industry Experience\"/\"Health & Travel Account Modification\"/\"Information Technology\"<br>3. 数量0-3项 |\n" +
"| educationRequirements | 结构+可选值 | 1. 结构:对象,含 academicMajor、degree<br>2. academicMajor仅允许\"Computer Science\"<br>3. degree仅允许\"Bachelor's Degree\"/\"Bachelor's degree or equivalent\" |\n" +
"| certificationsLicensesList | 结构+取值规则 | 1. 结构:数组,每个元素含 type、val<br>2. type仅允许\"standard\"/\"customize\"<br>3. valstandard仅允许\"AWS Certified Solutions Architect\"/\"CISSP Certified Information Systems Security Professional\"/\"PMP Certified\"<br>4. 数量0-3项 |\n" +
"\n" + "\n" +
"## 核心要求\n" + "**jobExperience Conversion Rules**:\n" +
"1. 所有字段必须遵守「业务约束规则表」,禁止生成规则外的值;\n" + "- \"0-1 years\" or \"Fresh graduate\" → 1\n" +
"2. 数值字段必须匹配类型如salaryStart为数字非字符串\n" + "- \"1-2 years\" → 2\n" +
"3. 必须包含requiredSkillsList、niceToHaveSkillsList、educationRequirements、certificationsLicensesList、descriptionTone、aboutRole、responsibilities、roleBenefitsList八个核心字段不得遗漏\n" + "- \"2-3 years\" → 3 \n" +
"4. 去掉以下字段createTime、updateTime、uuid、sysUserType、speechSpeed、acceptEquivalentWorkFlag生成结果中不得出现这些字段\n" + "- \"3-5 years\" → 4\n" +
"5. requiredSkillsList/niceToHaveSkillsList/educationRequirements/certificationsLicensesList的取值必须完全匹配业务约束规则\n" + "- \"5-8 years\" → 6\n" +
"6. jobExperience字段为自由文本输入不限制于预定义选项\n" + "- \"8-10 years\" → 8\n" +
"7. descriptionTone必须从四个选项中选择一个以数组格式返回\n" + "- \"10+ years\" or \"Senior level\" → 10\n" +
"8. aboutRole和responsibilities为文本字段需要详细描述\n" + "- If description is vague, infer from job level: Entry→1-2, Junior→2-3, Mid→3-5, Senior→5-8, Lead/Manager→8-10\n" +
"9. roleBenefitsList为数组格式包含3-8项岗位福利。\n" + "| locationType | Optional Value Set | Only allow: On-site/Remote/Hybrid |\n" +
"| status | Optional Value Set | Only allow: pause/archived/open/editing |\n" +
"| dataType | Optional Value Set | Only allow: release/draft/template |\n" +
"| salaryStart/salaryEnd | Numeric Range+Type | Type: Number (BigDecimal); Range: salaryStart≥50000, salaryEnd≤200000, and salaryEnd≥salaryStart |\n" +
"| descriptionTone | Optional Value Set | Only allow: professional/friendly/innovative/dynamic (choose one, array format) |\n" +
"| aboutRole | Text Input | Detailed description about the role, including background, importance, and development prospects |\n" +
"| responsibilities | Text Input | Detailed job responsibilities description, including main work content and specific tasks |\n" +
"| roleBenefitsList | Structure+Value Rules | 1. Structure: Array, each element contains only keyValue field;<br>2. Values: Free text such as \"Health Insurance\", \"Annual Bonus\", \"Paid Time Off\", etc.;<br>3. Quantity: 3-8 items |\n" +
"| requiredSkillsList | Structure+Value Rules | 1. Structure: Array, each element contains only keyValue field;<br>2. Values: Must be \"Angular 4+ & RxJS\"/\"Financial Services Industry Experience\"/\"Health & Travel Account Modification\"/\"Information Technology\";<br>3. Quantity: 1-5 items |\n" +
"| niceToHaveSkillsList | Structure+Value Rules | 1. Structure: Array, each element contains only keyValue field;<br>2. Values: Must be \"Financial Services Industry Experience\"/\"Health & Travel Account Modification\"/\"Information Technology\";<br>3. Quantity: 0-3 items |\n" +
"| educationRequirements | Structure+Optional Values | 1. Structure: Object containing academicMajor, degree;<br>2. academicMajor: Only allow \"Computer Science\";<br>3. degree: Only allow \"Bachelor's Degree\"/\"Bachelor's degree or equivalent\" |\n" +
"| certificationsLicensesList | Structure+Value Rules | 1. Structure: Array, each element contains type, val;<br>2. type: Only allow \"standard\"/\"customize\";<br>3. val (standard): Only allow \"AWS Certified Solutions Architect\"/\"CISSP Certified Information Systems Security Professional\"/\"PMP Certified\";<br>4. Quantity: 0-3 items |\n" +
"\n" + "\n" +
"## API响应格式要求\n" + "## Core Requirements\n" +
"- 顶层字段code固定0、dataHotakeRolesInfoDtoRes、message空字符串、timestamp当前时间格式YYYY-MM-DDTHH:MM:SS\n" + "1. All fields must comply with the \"Business Constraint Rules Table\", and values outside the rules are prohibited;\n" +
"- 严格排除createTime、updateTime、uuid、sysUserType、speechSpeed、acceptEquivalentWorkFlag\n" + "2. Numeric fields must match the type (e.g., salaryStart is a number, not a string);\n" +
"- 严格匹配字段类型(如数组字段不得为单个值,数字字段不得为字符串)。\n" + "3. Must include the eight core fields: requiredSkillsList, niceToHaveSkillsList, educationRequirements, certificationsLicensesList, descriptionTone, aboutRole, responsibilities, roleBenefitsList, and must not be omitted;\n" +
"4. Remove the following fields: createTime, updateTime, uuid, sysUserType, speechSpeed, acceptEquivalentWorkFlag (these fields must not appear in the generated results);\n" +
"5. The values of requiredSkillsList/niceToHaveSkillsList/educationRequirements/certificationsLicensesList must completely match the business constraint rules;\n" +
"6. jobExperience field is integer type, value range 1-10, representing required years of work experience;\n" +
"7. descriptionTone must choose one from four options, returned in array format;\n" +
"8. aboutRole and responsibilities are text fields that require detailed descriptions;\n" +
"9. roleBenefitsList is in array format, containing 3-8 role benefits.\n" +
"\n" + "\n" +
"## 输出格式\n" + "## API Response Format Requirements\n" +
"请以JSON格式返回补全结果确保\n" + "- Top-level fields: code (fixed 0), data (HotakeRolesInfoDtoRes), message (empty string), timestamp (current time, format YYYY-MM-DDTHH:MM:SS);\n" +
"1. 结构完整,包含所有必需字段\n" + "- Strictly exclude: createTime, updateTime, uuid, sysUserType, speechSpeed, acceptEquivalentWorkFlag;\n" +
"2. 数据类型正确(数字、字符串、数组、对象)\n" + "- Strictly match field types (e.g., array fields must not be single values, numeric fields must not be strings).\n" +
"3. 取值符合业务约束规则\n" +
"4. 不包含禁止字段\n" +
"\n" + "\n" +
"## 示例输出格式\n" + "## Output Format\n" +
"Please return the completion results in JSON format, ensuring:\n" +
"1. Complete structure with all required fields\n" +
"2. Correct data types (numbers, strings, arrays, objects)\n" +
"3. Values comply with business constraint rules\n" +
"4. Does not contain prohibited fields\n" +
"\n" +
"## Example Output Format\n" +
"```json\n" + "```json\n" +
"{\n" + "{\n" +
" \"code\": 0,\n" + " \"code\": 0,\n" +
" \"data\": {\n" + " \"data\": {\n" +
" \"jobTitle\": \"岗位名称\",\n" + " \"jobTitle\": \"Job Title\",\n" +
" \"companyName\": \"公司名称\",\n" + " \"companyName\": \"Company Name\",\n" +
" \"location\": \"工作地点\",\n" + " \"location\": \"Work Location\",\n" +
" \"salaryStart\": 80000,\n" + " \"salaryStart\": 80000,\n" +
" \"salaryEnd\": 120000,\n" + " \"salaryEnd\": 120000,\n" +
" \"jobType\": \"Full-time\",\n" + " \"jobType\": \"Full-time\",\n" +
" \"jobLevel\": \"Senior\",\n" + " \"jobLevel\": \"Senior\",\n" +
" \"jobExperience\": \"3-5年相关工作经验熟悉Java开发\",\n" + " \"jobExperience\": 4,\n" +
" \"locationType\": \"On-site\",\n" + " \"locationType\": \"On-site\",\n" +
" \"status\": \"open\",\n" + " \"status\": \"open\",\n" +
" \"dataType\": \"release\",\n" + " \"dataType\": \"release\",\n" +
" \"descriptionTone\": [\"professional\"],\n" + " \"descriptionTone\": [\"professional\"],\n" +
" \"aboutRole\": \"这是一个关键的技术岗位,负责公司核心业务系统的开发和维护。该职位将直接参与产品架构设计,是技术团队的重要组成部分,具有良好的职业发展前景。\",\n" + " \"aboutRole\": \"This is a key technical position responsible for developing and maintaining the company's core business systems. The role will directly participate in product architecture design and is an important part of the technical team with excellent career development prospects.\",\n" +
" \"responsibilities\": \"1. 负责后端系统的设计、开发和维护2. 参与系统架构设计和技术方案制定3. 编写高质量、可维护的代码4. 与前端团队协作完成产品功能5. 参与代码审查和技术分享6. 解决系统性能和稳定性问题。\",\n" + " \"responsibilities\": \"1. Design, develop and maintain backend systems; 2. Participate in system architecture design and technical solution formulation; 3. Write high-quality, maintainable code; 4. Collaborate with frontend teams to complete product features; 5. Participate in code reviews and technical sharing; 6. Solve system performance and stability issues.\",\n" +
" \"roleBenefitsList\": [\n" + " \"roleBenefitsList\": [\n" +
" {\"keyValue\": \"五险一金\"},\n" + " {\"keyValue\": \"Health Insurance\"},\n" +
" {\"keyValue\": \"年终奖金\"},\n" + " {\"keyValue\": \"Annual Bonus\"},\n" +
" {\"keyValue\": \"带薪年假\"},\n" + " {\"keyValue\": \"Paid Time Off\"},\n" +
" {\"keyValue\": \"弹性工作时间\"},\n" + " {\"keyValue\": \"Flexible Working Hours\"},\n" +
" {\"keyValue\": \"技能培训\"},\n" + " {\"keyValue\": \"Skills Training\"},\n" +
" {\"keyValue\": \"团队建设活动\"}\n" + " {\"keyValue\": \"Team Building Activities\"}\n" +
" ],\n" + " ],\n" +
" \"requiredSkillsList\": [\n" + " \"requiredSkillsList\": [\n" +
" {\"keyValue\": \"Information Technology\"},\n" + " {\"keyValue\": \"Information Technology\"},\n" +
@@ -1814,12 +1824,255 @@ public class AiCommonPromptConstants {
"}\n" + "}\n" +
"```\n" + "```\n" +
"\n" + "\n" +
"## 质量要求\n" + "## Quality Requirements\n" +
"1. 信息保留性:准确保留第一阶段提取的有效信息\n" + "1. Information Retention: Accurately retain valid information extracted in the first stage\n" +
"2. 补全合理性AI补全的信息应符合行业标准和岗位特点\n" + "2. Completion Reasonableness: AI-completed information should comply with industry standards and job characteristics\n" +
"3. 规则严格性100%遵守业务约束规则,不得有任何例外\n" + "3. Rule Strictness: 100% compliance with business constraint rules, no exceptions allowed\n" +
"4. 格式标准性严格按照API格式要求输出\n" + "4. Format Standardization: Strictly output according to API format requirements\n" +
"5. 完整性验证:确保所有必需字段都已包含"; "5. Completeness Verification: Ensure all required fields are included";
return promptStr;
}
/**
* 初始化AI生成问题提示词
* @return
*/
public static String initializationAiInterviewQuestionsPrompt(){
String promptStr = "You are a senior AI interviewer and HR expert, specializing in designing high-quality standardized interview questions for AI voice interview systems.\n" +
"\n" +
"## Core Mission\n" +
"Based on the provided job description, generate a set of standardized interview questions for a 15-25 minute AI voice interview. All candidates applying for the same position will answer the same set of questions.\n" +
"\n" +
"## AI Interview Characteristics\n" +
"\n" +
"### 1. Standardization Principle\n" +
"- All candidates face the same questions\n" +
"- Questions based on job requirements, not individual resumes\n" +
"- Ensure fairness and consistency\n" +
"\n" +
"### 2. Time Control\n" +
"- Total interview duration strictly controlled within 15-25 minutes\n" +
"- Each question estimated response time 2-4 minutes\n" +
"- Include thinking time and expression time\n" +
"\n" +
"### 3. AI Voice Adaptation\n" +
"- Questions clearly and unambiguously stated\n" +
"- Suitable for voice interaction, not dependent on visual materials\n" +
"- Easy for AI systems to understand and evaluate responses\n" +
"\n" +
"## Question Category Requirements\n" +
"\n" +
"### 1. Technical Skills\n" +
"**Objective**: Assess candidate's professional technical capabilities\n" +
"\n" +
"**Question Characteristics**:\n" +
"- Based on tech stack and requirements in job description\n" +
"- Test theoretical knowledge and practical experience\n" +
"- Include scenario-based technical questions\n" +
"- Evaluate learning ability and technical depth\n" +
"\n" +
"**Example Areas**:\n" +
"- Programming languages and framework usage\n" +
"- System design and architectural thinking\n" +
"- Tools and platform experience\n" +
"- Best practices and code quality\n" +
"- New technology learning and application\n" +
"\n" +
"### 2. Experience Level\n" +
"**Objective**: Understand candidate's work experience and project background\n" +
"\n" +
"**Question Characteristics**:\n" +
"- Don't involve specific company or project names\n" +
"- Focus on depth and breadth of experience\n" +
"- Evaluate problem-solving capabilities\n" +
"- Understand career development trajectory\n" +
"\n" +
"**Example Content**:\n" +
"- Years of experience and experience level\n" +
"- Project complexity and scale\n" +
"- Team collaboration experience\n" +
"- Challenge handling ability\n" +
"- Growth and learning experiences\n" +
"\n" +
"### 3. Problem Solving\n" +
"**Objective**: Assess candidate's analytical and problem-solving abilities\n" +
"\n" +
"**Question Characteristics**:\n" +
"- Scenario-based hypothetical questions\n" +
"- Test logical thinking and analytical skills\n" +
"- Evaluate innovation and optimization thinking\n" +
"- Understand decision-making process\n" +
"\n" +
"**Example Scenarios**:\n" +
"- Technical problem resolution\n" +
"- Performance optimization challenges\n" +
"- System failure handling\n" +
"- Requirement change response\n" +
"- Solutions under resource constraints\n" +
"\n" +
"### 4. Communication\n" +
"**Objective**: Assess candidate's expression and communication abilities\n" +
"\n" +
"**Question Characteristics**:\n" +
"- Test technical concept explanation ability\n" +
"- Evaluate cross-team collaboration communication\n" +
"- Understand conflict resolution approaches\n" +
"- Verify knowledge transfer capabilities\n" +
"\n" +
"### 5. Cultural Fit\n" +
"**Objective**: Assess candidate's compatibility with company culture\n" +
"\n" +
"**Question Characteristics**:\n" +
"- Based on company culture and values\n" +
"- Understand work attitude and values\n" +
"- Evaluate team collaboration awareness\n" +
"- Verify career development expectations\n" +
"\n" +
"## Time Allocation Recommendations\n" +
"\n" +
"### 15-minute Interview (6-7 questions)\n" +
"- Technical Skills: 3-4 questions (8-10 minutes)\n" +
"- Experience Level: 1-2 questions (3-4 minutes)\n" +
"- Problem Solving: 1 question (2-3 minutes)\n" +
"- Cultural Fit: 1 question (2 minutes)\n" +
"\n" +
"### 20-minute Interview (8-9 questions)\n" +
"- Technical Skills: 4-5 questions (10-12 minutes)\n" +
"- Experience Level: 2 questions (4-5 minutes)\n" +
"- Problem Solving: 1-2 questions (3-4 minutes)\n" +
"- Communication: 1 question (2-3 minutes)\n" +
"\n" +
"### 25-minute Interview (10-11 questions)\n" +
"- Technical Skills: 5-6 questions (12-15 minutes)\n" +
"- Experience Level: 2-3 questions (5-6 minutes)\n" +
"- Problem Solving: 2 questions (4-5 minutes)\n" +
"- Communication: 1 question (2-3 minutes)\n" +
"- Cultural Fit: 1 question (2 minutes)\n" +
"\n" +
"## Question Design Principles\n" +
"\n" +
"### 1. Open-ended Principle\n" +
"- Avoid simple yes/no questions\n" +
"- Encourage detailed explanations and examples\n" +
"- Allow multiple correct answers\n" +
"- Provide ample expression space\n" +
"\n" +
"### 2. Relevance Principle\n" +
"- Strictly based on job description and requirements\n" +
"- Related to actual work scenarios\n" +
"- Match position level and experience requirements\n" +
"- Reflect core skills and abilities\n" +
"\n" +
"### 3. Fairness Principle\n" +
"- Don't involve personal background and privacy\n" +
"- Avoid cultural bias and discrimination\n" +
"- Give all candidates equal opportunities\n" +
"- Assess based on ability, not background\n" +
"\n" +
"### 4. Assessability Principle\n" +
"- Easy for AI systems to analyze and score\n" +
"- Have clear evaluation dimensions\n" +
"- Quantifiable assessment criteria\n" +
"- Support objective comparative analysis\n" +
"\n" +
"## Output Format Requirements\n" +
"\n" +
"Please return AI interview questions in JSON format with the following structure:\n" +
"\n" +
"```json\n" +
"{\n" +
" \"interview_questions\": [\n" +
" {\n" +
" \"question_id\": \"Q001\",\n" +
" \"question_text\": \"Please describe your experience with [relevant technology] and how you learned and mastered this technology?\",\n" +
" \"category\": \"technical_skills|experience_level|problem_solving|communication|cultural_fit\",\n" +
" \"estimated_time\": 3.0,\n" +
" \"difficulty_level\": \"basic|intermediate|advanced\",\n" +
" \"evaluation_criteria\": \"Assess technical depth, learning ability, practical experience\",\n" +
" \"follow_up_hints\": [\"Can ask about specific technical details\", \"Understand challenges encountered and solutions\"],\n" +
" \"expected_answer_points\": [\"Technical understanding level\", \"Learning methods\", \"Practical application\", \"Continuous improvement\"],\n" +
" \"scoring_weight\": 1-5\n" +
" }\n" +
" ],\n" +
" \"interview_summary\": {\n" +
" \"total_questions\": 8,\n" +
" \"estimated_total_time\": 20,\n" +
" \"category_distribution\": {\n" +
" \"technical_skills\": 4,\n" +
" \"experience_level\": 2,\n" +
" \"problem_solving\": 1,\n" +
" \"cultural_fit\": 1\n" +
" },\n" +
" \"difficulty_distribution\": {\n" +
" \"basic\": 2,\n" +
" \"intermediate\": 5,\n" +
" \"advanced\": 1\n" +
" }\n" +
" },\n" +
" \"interview_guidelines\": {\n" +
" \"introduction\": \"Interview opening statement suggestion\",\n" +
" \"transition_phrases\": [\"Transition phrase 1\", \"Transition phrase 2\"],\n" +
" \"closing_statement\": \"Interview closing statement\",\n" +
" \"time_management_tips\": [\"Time control tip 1\", \"Time control tip 2\"]\n" +
" },\n" +
" \"evaluation_framework\": {\n" +
" \"scoring_method\": \"Scoring method description\",\n" +
" \"key_indicators\": [\"Key evaluation indicator 1\", \"Key evaluation indicator 2\"],\n" +
" \"red_flags\": [\"Red flag to watch for 1\", \"Red flag to watch for 2\"],\n" +
" \"positive_signals\": [\"Positive signal 1\", \"Positive signal 2\"]\n" +
" }\n" +
"}\n" +
"```\n" +
"\n" +
"## Quality Standards\n" +
"\n" +
"### 1. Question Quality\n" +
"- Clear and accurate expression, unambiguous\n" +
"- Suitable for voice interaction scenarios\n" +
"- Match job requirements and level\n" +
"- Have good discriminative power\n" +
"\n" +
"### 2. Time Control\n" +
"- Total duration strictly controlled within specified range\n" +
"- Reasonable time allocation for each question\n" +
"- Consider thinking and expression time\n" +
"- Leave appropriate buffer time\n" +
"\n" +
"### 3. Structural Completeness\n" +
"- Include all required fields\n" +
"- Balanced and reasonable question distribution\n" +
"- Appropriate difficulty gradient design\n" +
"- Complete evaluation framework\n" +
"\n" +
"### 4. AI Adaptability\n" +
"- Easy for AI systems to process and analyze\n" +
"- Support automated evaluation\n" +
"- Suitable for speech recognition and understanding\n" +
"- Easy to quantify and compare results\n" +
"\n" +
"## Special Requirements\n" +
"\n" +
"### Technical Question Design\n" +
"1. **Based on Job Tech Stack**: Strictly based on technical requirements in job description\n" +
"2. **Scenario Application**: Design questions combining actual work scenarios\n" +
"3. **Progressive Levels**: Gradually deepen from basic concepts to advanced applications\n" +
"4. **Practice-Oriented**: Emphasize practical application ability over pure theoretical knowledge\n" +
"\n" +
"### Non-Technical Question Design\n" +
"1. **Cultural Fit**: Design based on company culture and values\n" +
"2. **Universal Applicability**: Suitable for candidates from different backgrounds\n" +
"3. **Behavioral Assessment**: Evaluate behavioral patterns through hypothetical scenarios\n" +
"4. **Development Potential**: Assess learning ability and growth potential\n" +
"\n" +
"### Things to Avoid\n" +
"1. Must not involve personal privacy and sensitive information\n" +
"2. Avoid questions requiring specific tools or environments\n" +
"3. Don't use overly complex or academic expressions\n" +
"4. Avoid cultural bias and discriminatory content\n" +
"5. Don't design complex algorithm questions requiring long thinking time\n" +
"\n" +
"Please generate a high-quality, standardized set of AI interview questions based on the provided job information.\n";
return promptStr; return promptStr;
} }

View File

@@ -0,0 +1,56 @@
package com.vetti.common.enums;
public enum RoleBenefitsEnum {
health_insurance("health_insurance", "health insurance"),
paid_time_off("paid_time_off", "paid time off"),
flexible_work_schedule("flexible_work_schedule", "flexible work schedule"),
professional_development_budget("professional_development_budget", "professional development budget"),
performance_bonuses("performance_bonuses", "performance bonuses"),
wellness_program("wellness_program", "wellness program"),
stock_options("stock_options", "stock options/esop"),
relocation_support("relocation_support", "relocation support"),
parental_leave("parental_leave", "parental leave"),
team_events_corporate_activities("team_events_corporate_activities", "team events & corporate activities"),
;
private final String code;
private final String info;
RoleBenefitsEnum(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
public static RoleBenefitsEnum getByInfo(String code) {
RoleBenefitsEnum[] entityEnums = values();
for (RoleBenefitsEnum entityEnum : entityEnums) {
if (entityEnum.getCode().equals(code)) {
return entityEnum;
}
}
return null;
}
public static RoleBenefitsEnum getByCode(String info) {
RoleBenefitsEnum[] entityEnums = values();
for (RoleBenefitsEnum entityEnum : entityEnums) {
if (entityEnum.getInfo().equals(info.toLowerCase())) {
return entityEnum;
}
}
return null;
}
}

View File

@@ -6,6 +6,8 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import cn.hutool.core.util.ObjectUtil;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import com.vetti.common.constant.Constants; import com.vetti.common.constant.Constants;
import com.vetti.common.core.text.StrFormatter; import com.vetti.common.core.text.StrFormatter;
@@ -719,4 +721,11 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
} }
return sb.toString(); return sb.toString();
} }
public static String getObjectStr(Object obj){
if(ObjectUtil.isNotEmpty(obj)){
return obj.toString();
}
return "";
}
} }

View File

@@ -114,7 +114,7 @@ public class SecurityConfig
// 对于登录login 注册register 验证码captchaImage 允许匿名访问 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login", "/register", "/captchaImage","/aiCommon/**","/voice-websocket/multiple/**", requests.antMatchers("/login", "/register", "/captchaImage","/aiCommon/**","/voice-websocket/multiple/**",
"/voice-websocket/**","/voice-websocket/multiplePcm/**","/verification/email/send","/verification/email/verify","/verification/phone/send", "/voice-websocket/**","/voice-websocket/multiplePcm/**","/verification/email/send","/verification/email/verify","/verification/phone/send",
"/forgotPassword","/verification/email/register/send").permitAll() "/forgotPassword","/verification/email/register/send","/voice-websocket/elevenLabsAgent/**").permitAll()
// 静态资源,可匿名访问 // 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()

View File

@@ -2,6 +2,7 @@ package com.vetti.hotake.service;
import com.vetti.hotake.domain.HotakeAiInterviewQuestionsInfo; import com.vetti.hotake.domain.HotakeAiInterviewQuestionsInfo;
import com.vetti.hotake.domain.HotakeInitialScreeningQuestionsInfo; import com.vetti.hotake.domain.HotakeInitialScreeningQuestionsInfo;
import com.vetti.hotake.domain.HotakeRolesInfo;
import com.vetti.hotake.domain.dto.*; import com.vetti.hotake.domain.dto.*;
import com.vetti.hotake.domain.vo.*; import com.vetti.hotake.domain.vo.*;
@@ -111,4 +112,20 @@ public interface IHotakeAiCommonToolsService {
public HotakeRolesInfoDto getRoleLinkAnalysis(HotakeRoleLinkAnalysisVo roleLinkAnalysisVo); public HotakeRolesInfoDto getRoleLinkAnalysis(HotakeRoleLinkAnalysisVo roleLinkAnalysisVo);
/**
* 招聘链接信息分析补全(按照步骤生成)
* @param rolesInfoDto 岗位信息
* @return
*/
public HotakeRolesInfoDto getRoleInfoAnalysisSetUp(HotakeRolesInfoDto rolesInfoDto);
/**
* AI面试问题生成
* @param rolesInfo 岗位信息
* @return
*/
public String getAiInterviewQuestions(HotakeRolesInfo rolesInfo);
} }

View File

@@ -1,12 +1,15 @@
package com.vetti.hotake.service.impl; package com.vetti.hotake.service.impl;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.vetti.common.ai.gpt.ChatGPTClient; import com.vetti.common.ai.gpt.ChatGPTClient;
import com.vetti.common.constant.AiCommonPromptConstants; import com.vetti.common.constant.AiCommonPromptConstants;
import com.vetti.common.core.domain.entity.SysUser; import com.vetti.common.core.domain.entity.SysUser;
import com.vetti.common.core.service.BaseServiceImpl; import com.vetti.common.core.service.BaseServiceImpl;
import com.vetti.common.enums.FillTypeEnum;
import com.vetti.common.enums.RoleBenefitsEnum;
import com.vetti.common.utils.SecurityUtils; import com.vetti.common.utils.SecurityUtils;
import com.vetti.common.utils.html.ReadHtmlByOkHttp; import com.vetti.common.utils.html.ReadHtmlByOkHttp;
import com.vetti.hotake.domain.HotakeInitScreQuestionsReplyRecordInfo; import com.vetti.hotake.domain.HotakeInitScreQuestionsReplyRecordInfo;
@@ -765,7 +768,7 @@ public class HotakeAiCommonToolsServiceImpl extends BaseServiceImpl implements I
String resultJson = "{\"jobTitle\":\"Senior Software Engineer\",\"companyName\":\"Tech Innovation Corp\",\"jobType\":null}"; String resultJson = "{\"jobTitle\":\"Senior Software Engineer\",\"companyName\":\"Tech Innovation Corp\",\"jobType\":null}";
log.info("招聘链接信息提取:{}",resultJson); log.info("招聘链接信息提取:{}",resultJson);
//处理岗位信息补充 //处理岗位信息补充
String userPrompt_1 = "请根据以下提取的岗位信息生成完整的API格式数据\\n\\n" +resultJson; String userPrompt_1 = "Please generate complete API-formatted data based on the extracted job information below\\n\\n" +resultJson;
List<Map<String, String>> listOne = new LinkedList(); List<Map<String, String>> listOne = new LinkedList();
Map<String, String> mapEntityOne = new HashMap<>(); Map<String, String> mapEntityOne = new HashMap<>();
mapEntityOne.put("role", "system"); mapEntityOne.put("role", "system");
@@ -798,25 +801,192 @@ public class HotakeAiCommonToolsServiceImpl extends BaseServiceImpl implements I
hotakeRolesInfoDto.setResponsibilities(dataMap.get("responsibilities").toString()); hotakeRolesInfoDto.setResponsibilities(dataMap.get("responsibilities").toString());
//岗位所需技能信息 //岗位所需技能信息
List<RequiredSkillsDto> requiredSkillsList = (List<RequiredSkillsDto>)dataMap.get("requiredSkills"); List<RequiredSkillsDto> requiredSkillsList = (List<RequiredSkillsDto>)dataMap.get("requiredSkillsList");
hotakeRolesInfoDto.setRequiredSkillsList(requiredSkillsList); hotakeRolesInfoDto.setRequiredSkillsList(requiredSkillsList);
hotakeRolesInfoDto.setRequiredSkillsJson(JSONUtil.toJsonStr(requiredSkillsList));
List<NiceToHaveSkillsDto> niceToHaveSkillsList = (List<NiceToHaveSkillsDto>)dataMap.get("niceToHaveSkillsList"); List<NiceToHaveSkillsDto> niceToHaveSkillsList = (List<NiceToHaveSkillsDto>)dataMap.get("niceToHaveSkillsList");
hotakeRolesInfoDto.setNiceToHaveSkillsList(niceToHaveSkillsList); hotakeRolesInfoDto.setNiceToHaveSkillsList(niceToHaveSkillsList);
List<RoleBenefitsDto> roleBenefitsList = (List<RoleBenefitsDto>)dataMap.get("roleBenefitsList"); hotakeRolesInfoDto.setNiceToHaveSkillsJson(JSONUtil.toJsonStr(niceToHaveSkillsList));
List<Map> roleBenefitsMapList = (List<Map>)dataMap.get("roleBenefitsList");
List<RoleBenefitsDto> roleBenefitsList = new ArrayList<>();
if(CollectionUtil.isNotEmpty(roleBenefitsMapList)){
for(Map dto : roleBenefitsMapList){
//福利转换
RoleBenefitsDto dto1 = new RoleBenefitsDto();
if(ObjectUtil.isNotEmpty(dto.get("keyValue"))){
RoleBenefitsEnum benefitsEnum = RoleBenefitsEnum.getByCode(dto.get("keyValue").toString());
if(benefitsEnum != null){
dto1.setKeyValue(benefitsEnum.getCode());
roleBenefitsList.add(dto1);
}
}
}
}
hotakeRolesInfoDto.setRoleBenefitsList(roleBenefitsList); hotakeRolesInfoDto.setRoleBenefitsList(roleBenefitsList);
hotakeRolesInfoDto.setRoleBenefitsJson(JSONUtil.toJsonStr(roleBenefitsList));
Map educationRequirementsMap = (Map)dataMap.get("educationRequirements"); Map educationRequirementsMap = (Map)dataMap.get("educationRequirements");
EducationRequirementsDto educationRequirements = new EducationRequirementsDto(); EducationRequirementsDto educationRequirements = new EducationRequirementsDto();
educationRequirements.setAcademicMajor(educationRequirementsMap.get("academicMajor").toString()); educationRequirements.setAcademicMajor(educationRequirementsMap.get("academicMajor").toString());
educationRequirements.setDegree(educationRequirementsMap.get("degree").toString()); educationRequirements.setDegree(educationRequirementsMap.get("degree").toString());
hotakeRolesInfoDto.setEducationRequirements(educationRequirements); hotakeRolesInfoDto.setEducationRequirements(educationRequirements);
hotakeRolesInfoDto.setEducationRequirementsJson(JSONUtil.toJsonStr(educationRequirements));
List<CertificationsLicensesDto> certificationsLicensesList = (List<CertificationsLicensesDto>)dataMap.get("certificationsLicensesList"); List<CertificationsLicensesDto> certificationsLicensesList = (List<CertificationsLicensesDto>)dataMap.get("certificationsLicensesList");
hotakeRolesInfoDto.setCertificationsLicensesList(certificationsLicensesList); hotakeRolesInfoDto.setCertificationsLicensesList(certificationsLicensesList);
hotakeRolesInfoDto.setCertificationsLicensesJson(JSONUtil.toJsonStr(certificationsLicensesList));
fill(FillTypeEnum.INSERT.getCode(),hotakeRolesInfoDto);
hotakeRolesInfoMapper.insertHotakeRolesInfo(hotakeRolesInfoDto);
return hotakeRolesInfoDto; return hotakeRolesInfoDto;
} }
/**
* 招聘链接信息分析补全
* @param roleLinkAnalysisVo 岗位链接对象
* @return
*/
@Override
public HotakeRolesInfoDto getRoleInfoAnalysisSetUp(HotakeRolesInfoDto rolesInfoDto) {
String prompt = AiCommonPromptConstants.initializationRoleLinkAnalysisPrompt();
String resultJson = "{\n" +
" \"jobTitle\": "+rolesInfoDto.getRoleName()+",\n" +
" \"companyName\": "+rolesInfoDto.getCompanyName()+",\n" +
" \"location\": "+rolesInfoDto.getLocations()+",\n" +
" \"salaryRange\": "+rolesInfoDto.getSalaryStart()+"-"+rolesInfoDto.getSalaryEnd()+",\n" +
" \"jobType\": "+rolesInfoDto.getJobType()+",\n" +
" \"experience\": "+rolesInfoDto.getJobExperience()+",\n" +
" \"education\": "+JSONUtil.toJsonStr(rolesInfoDto.getEducationRequirements())+",\n" +
" \"skills\": "+JSONUtil.toJsonStr(rolesInfoDto.getRequiredSkillsList())+",\n" +
" \"description\": "+rolesInfoDto.getAboutRole()+"\n" +
"}";
log.info("招聘链接信息提取:{}",resultJson);
//处理岗位信息补充
String userPrompt_1 = "Please generate complete API-formatted data based on the extracted job information below\\n\\n" +resultJson;
List<Map<String, String>> listOne = new LinkedList();
Map<String, String> mapEntityOne = new HashMap<>();
mapEntityOne.put("role", "system");
mapEntityOne.put("content",prompt);
listOne.add(mapEntityOne);
Map<String, String> mapUserEntityOne = new HashMap<>();
mapUserEntityOne.put("role", "user");
mapUserEntityOne.put("content",userPrompt_1);
listOne.add(mapUserEntityOne);
String promptJsonOne = JSONUtil.toJsonStr(listOne);
String resultStrOne = chatGPTClient.handleAiChat(promptJsonOne,"RLINKAL");
String resultJsonOne = resultStrOne.replaceAll("```json","").replaceAll("```","");
log.info("招聘信息补全:{}",resultJsonOne);
HotakeRolesInfoDto hotakeRolesInfoDto = new HotakeRolesInfoDto();
Map resultMap = JSONUtil.toBean(resultJsonOne,Map.class);
Map dataMap = (Map)resultMap.get("data");
hotakeRolesInfoDto.setRoleName(dataMap.get("jobTitle").toString());
hotakeRolesInfoDto.setCompanyName(dataMap.get("companyName").toString());
hotakeRolesInfoDto.setLocations(dataMap.get("location").toString());
hotakeRolesInfoDto.setSalaryStart(new BigDecimal(dataMap.get("salaryStart").toString()));
hotakeRolesInfoDto.setSalaryEnd(new BigDecimal(dataMap.get("salaryEnd").toString()));
hotakeRolesInfoDto.setJobType(dataMap.get("jobType").toString().toLowerCase());
hotakeRolesInfoDto.setJobLevel(dataMap.get("jobLevel").toString().toLowerCase());
hotakeRolesInfoDto.setJobExperience("5");
hotakeRolesInfoDto.setLocationType(dataMap.get("locationType").toString().toLowerCase());
hotakeRolesInfoDto.setAboutRole(dataMap.get("aboutRole").toString());
hotakeRolesInfoDto.setResponsibilities(dataMap.get("responsibilities").toString());
//岗位所需技能信息
List<RequiredSkillsDto> requiredSkillsList = (List<RequiredSkillsDto>)dataMap.get("requiredSkillsList");
hotakeRolesInfoDto.setRequiredSkillsList(requiredSkillsList);
hotakeRolesInfoDto.setRequiredSkillsJson(JSONUtil.toJsonStr(requiredSkillsList));
List<NiceToHaveSkillsDto> niceToHaveSkillsList = (List<NiceToHaveSkillsDto>)dataMap.get("niceToHaveSkillsList");
hotakeRolesInfoDto.setNiceToHaveSkillsList(niceToHaveSkillsList);
hotakeRolesInfoDto.setNiceToHaveSkillsJson(JSONUtil.toJsonStr(niceToHaveSkillsList));
List<Map> roleBenefitsMapList = (List<Map>)dataMap.get("roleBenefitsList");
List<RoleBenefitsDto> roleBenefitsList = new ArrayList<>();
if(CollectionUtil.isNotEmpty(roleBenefitsMapList)){
for(Map dto : roleBenefitsMapList){
//福利转换
RoleBenefitsDto dto1 = new RoleBenefitsDto();
if(ObjectUtil.isNotEmpty(dto.get("keyValue"))){
RoleBenefitsEnum benefitsEnum = RoleBenefitsEnum.getByCode(dto.get("keyValue").toString());
if(benefitsEnum != null){
dto1.setKeyValue(benefitsEnum.getCode());
roleBenefitsList.add(dto1);
}
}
}
}
hotakeRolesInfoDto.setRoleBenefitsList(roleBenefitsList);
hotakeRolesInfoDto.setRoleBenefitsJson(JSONUtil.toJsonStr(roleBenefitsList));
Map educationRequirementsMap = (Map)dataMap.get("educationRequirements");
EducationRequirementsDto educationRequirements = new EducationRequirementsDto();
educationRequirements.setAcademicMajor(educationRequirementsMap.get("academicMajor").toString());
educationRequirements.setDegree(educationRequirementsMap.get("degree").toString());
hotakeRolesInfoDto.setEducationRequirements(educationRequirements);
hotakeRolesInfoDto.setEducationRequirementsJson(JSONUtil.toJsonStr(educationRequirements));
List<CertificationsLicensesDto> certificationsLicensesList = (List<CertificationsLicensesDto>)dataMap.get("certificationsLicensesList");
hotakeRolesInfoDto.setCertificationsLicensesList(certificationsLicensesList);
hotakeRolesInfoDto.setCertificationsLicensesJson(JSONUtil.toJsonStr(certificationsLicensesList));
return hotakeRolesInfoDto;
}
/**
* AI面试问题生成
* @param rolesInfo 岗位信息
* @return
*/
@Override
public String getAiInterviewQuestions(HotakeRolesInfo rolesInfo) {
String prompt = AiCommonPromptConstants.initializationAiInterviewQuestionsPrompt();
String resultJson = "Please generate AI interview questions based on the following job description\n" +
"\n" +
"**Job Information**\n" +
"职位名称:【】\n" +
"技术要求:【】\n" +
"经验要求:【】\n" +
"面试时长:【】\n" +
"公司文化:【】\n" +
"特殊要求:【】\n" +
"`;";
log.info("招聘链接信息提取:{}",resultJson);
//处理岗位信息补充
String userPrompt_1 = "Please generate complete API-formatted data based on the extracted job information below\\n\\n" +resultJson;
List<Map<String, String>> listOne = new LinkedList();
Map<String, String> mapEntityOne = new HashMap<>();
mapEntityOne.put("role", "system");
mapEntityOne.put("content",prompt);
listOne.add(mapEntityOne);
Map<String, String> mapUserEntityOne = new HashMap<>();
mapUserEntityOne.put("role", "user");
mapUserEntityOne.put("content",userPrompt_1);
listOne.add(mapUserEntityOne);
String promptJsonOne = JSONUtil.toJsonStr(listOne);
String resultStrOne = chatGPTClient.handleAiChat(promptJsonOne,"RLINKAL");
String resultJsonOne = resultStrOne.replaceAll("```json","").replaceAll("```","");
log.info("招聘信息补全:{}",resultJsonOne);
return "";
}
} }

View File

@@ -10,10 +10,7 @@ import com.vetti.common.config.RuoYiConfig;
import com.vetti.common.core.service.BaseServiceImpl; import com.vetti.common.core.service.BaseServiceImpl;
import com.vetti.common.enums.FillTypeEnum; import com.vetti.common.enums.FillTypeEnum;
import com.vetti.common.enums.MinioBucketNameEnum; import com.vetti.common.enums.MinioBucketNameEnum;
import com.vetti.common.utils.HtmlToPdfUtil; import com.vetti.common.utils.*;
import com.vetti.common.utils.MarkdownUtil;
import com.vetti.common.utils.MultipartFileUtil;
import com.vetti.common.utils.SecurityUtils;
import com.vetti.common.utils.readFile.FileContentUtil; import com.vetti.common.utils.readFile.FileContentUtil;
import com.vetti.hotake.domain.HotakeCvInfo; import com.vetti.hotake.domain.HotakeCvInfo;
import com.vetti.hotake.domain.HotakeProblemBaseInfo; import com.vetti.hotake.domain.HotakeProblemBaseInfo;
@@ -231,7 +228,6 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
//修改的时候重新生成问题和评分 //修改的时候重新生成问题和评分
hotakeCvInfoMapper.updateHotakeCvInfo(hotakeCvInfo); hotakeCvInfoMapper.updateHotakeCvInfo(hotakeCvInfo);
return hotakeCvInfo; return hotakeCvInfo;
@@ -275,6 +271,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
/** /**
* 简历解析以及生成问题和评分 * 简历解析以及生成问题和评分
*
* @param hotakeCvInfo 简历信息 * @param hotakeCvInfo 简历信息
* @return * @return
*/ */
@@ -352,6 +349,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
/** /**
* 根据其他的附件信息生成个人简历 * 根据其他的附件信息生成个人简历
*
* @return * @return
*/ */
@Override @Override
@@ -371,7 +369,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
basicInformation = cvInfos.get(0).getAnalyzedAttachmentJson(); basicInformation = cvInfos.get(0).getAnalyzedAttachmentJson();
} }
List<HotakeCvInfo> cvInfoOnes = cvInfoList.stream().filter(e -> !"letter".equals(e.getCvFileType()) List<HotakeCvInfo> cvInfoOnes = cvInfoList.stream().filter(e -> !"letter".equals(e.getCvFileType())
&& "cv".equals(e.getCvFileType())).toList(); && !"cv".equals(e.getCvFileType())).toList();
if (CollectionUtil.isNotEmpty(cvInfoOnes)) { if (CollectionUtil.isNotEmpty(cvInfoOnes)) {
List<Map> attachmentContentList = new ArrayList<>(); List<Map> attachmentContentList = new ArrayList<>();
for (HotakeCvInfo hotakeCvInfo : cvInfoOnes) { for (HotakeCvInfo hotakeCvInfo : cvInfoOnes) {
@@ -391,17 +389,47 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
HotakeCvInfoDto cvInfoDto = new HotakeCvInfoDto(); HotakeCvInfoDto cvInfoDto = new HotakeCvInfoDto();
//个人基本信息 //个人基本信息
Map personalInfoMap = (Map) dataMap.get("personal_info"); Map personalInfoMap = (Map) dataMap.get("personal_info");
cvInfoDto.setName(personalInfoMap.get("name").toString()); if (personalInfoMap != null) {
cvInfoDto.setName(StringUtils.getObjectStr(personalInfoMap.get("full_name")));
Map contactDetailsMap = (Map) personalInfoMap.get("contact_details"); Map contactDetailsMap = (Map) personalInfoMap.get("contact_details");
cvInfoDto.setPhone(contactDetailsMap.get("phone").toString()); if (contactDetailsMap != null) {
cvInfoDto.setEmail(contactDetailsMap.get("email").toString()); cvInfoDto.setPhone(StringUtils.getObjectStr(contactDetailsMap.get("phone")));
cvInfoDto.setLocation(contactDetailsMap.get("address").toString()); cvInfoDto.setEmail(StringUtils.getObjectStr(contactDetailsMap.get("email")));
//个人介绍 cvInfoDto.setLocation(StringUtils.getObjectStr(contactDetailsMap.get("address")));
cvInfoDto.setAbout(dataMap.get("professional_summary").toString()); } else {
cvInfoDto.setPhone(StringUtils.getObjectStr(personalInfoMap.get("phone")));
cvInfoDto.setEmail(StringUtils.getObjectStr(personalInfoMap.get("email")));
cvInfoDto.setLocation(StringUtils.getObjectStr(personalInfoMap.get("address")));
}
}
//个人介绍
cvInfoDto.setAbout(StringUtils.getObjectStr(dataMap.get("professional_summary")));
try{
//技能工具 //技能工具
Map skillsCertificatesMap = (Map)dataMap.get("skills_certificates"); List<Map> skillsList = (List<Map>) dataMap.get("skills_certificates");
List<String> skillsList = (List<String>)skillsCertificatesMap.get("skills"); if(CollectionUtil.isNotEmpty(skillsList)){
Map map = skillsList.get(0);
if(map != null){
List<String> skillsAllList = (List<String>) map.get("skills");
//技能工具
List<VcSkillsToolsDto> skillsTools = new ArrayList<>();
if (CollectionUtil.isNotEmpty(skillsAllList)) {
for (String skill : skillsAllList) {
VcSkillsToolsDto toolsDto = new VcSkillsToolsDto();
toolsDto.setContent(skill);
skillsTools.add(toolsDto);
}
}
cvInfoDto.setSkillsTools(skillsTools);
}
}
}catch (Exception e){
//技能工具
List<String> skillsList = (List<String>) dataMap.get("skills_certificates");
if(CollectionUtil.isNotEmpty(skillsList)){
//技能工具 //技能工具
List<VcSkillsToolsDto> skillsTools = new ArrayList<>(); List<VcSkillsToolsDto> skillsTools = new ArrayList<>();
if (CollectionUtil.isNotEmpty(skillsList)) { if (CollectionUtil.isNotEmpty(skillsList)) {
@@ -412,6 +440,10 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
} }
} }
cvInfoDto.setSkillsTools(skillsTools); cvInfoDto.setSkillsTools(skillsTools);
}
}
//工作经验集合 //工作经验集合
List<VcExperienceDto> experience = new ArrayList<>(); List<VcExperienceDto> experience = new ArrayList<>();
List<Map> workExperienceMapList = (List<Map>) dataMap.get("work_experience"); List<Map> workExperienceMapList = (List<Map>) dataMap.get("work_experience");
@@ -419,16 +451,13 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
for (Map map : workExperienceMapList) { for (Map map : workExperienceMapList) {
VcExperienceDto experienceDto = new VcExperienceDto(); VcExperienceDto experienceDto = new VcExperienceDto();
experienceDto.setCompany(map.get("company").toString()); experienceDto.setCompany(StringUtils.getObjectStr(map.get("company")));
experienceDto.setTitle(map.get("position").toString()); experienceDto.setTitle(StringUtils.getObjectStr(map.get("job_title")));
String time_period = map.get("time_period").toString(); experienceDto.setLocation(StringUtils.getObjectStr(map.get("location")));
if(StrUtil.isNotEmpty(time_period)){ String startDate = StringUtils.getObjectStr(map.get("start_date"));
String[] times = time_period.split(""); experienceDto.setDurationStart(startDate);
experienceDto.setDurationStart(times[0]); String endDate = StringUtils.getObjectStr(map.get("end_date"));
if(times.length > 1){ experienceDto.setDurationEnd(endDate);
experienceDto.setDurationEnd(times[1]);
}
}
List<VcExperienceDescriptionDto> description = new ArrayList<>(); List<VcExperienceDescriptionDto> description = new ArrayList<>();
List<String> responsibilitiesList = (List<String>) map.get("responsibilities"); List<String> responsibilitiesList = (List<String>) map.get("responsibilities");
if (CollectionUtil.isNotEmpty(responsibilitiesList)) { if (CollectionUtil.isNotEmpty(responsibilitiesList)) {
@@ -449,9 +478,9 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
if (CollectionUtil.isNotEmpty(educationMapList)) { if (CollectionUtil.isNotEmpty(educationMapList)) {
for (Map map : educationMapList) { for (Map map : educationMapList) {
VcEducationDto educationDto = new VcEducationDto(); VcEducationDto educationDto = new VcEducationDto();
educationDto.setDegree(map.get("degree").toString()); educationDto.setDegree(StringUtils.getObjectStr(map.get("degree")));
educationDto.setInstitution(map.get("school").toString()); educationDto.setInstitution(StringUtils.getObjectStr(map.get("institution")));
String time_period = map.get("time_period").toString(); String time_period = StringUtils.getObjectStr(map.get("graduation_date"));
if (StrUtil.isNotEmpty(time_period)) { if (StrUtil.isNotEmpty(time_period)) {
String[] times = time_period.split(""); String[] times = time_period.split("");
educationDto.setDurationStart(times[0]); educationDto.setDurationStart(times[0]);
@@ -508,7 +537,8 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
insertHotakeCvInfo(cvInfo); insertHotakeCvInfo(cvInfo);
//保存简历PDF附件数据 //保存简历PDF附件数据
// log.info("Markdown数据为:{}",markdown); // log.info("Markdown数据为:{}",markdown);
}catch (Exception e){} } catch (Exception e) {
}
return cvInfo; return cvInfo;
} }
@@ -692,6 +722,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
/** /**
* 处理分析附件返回对应的json 内容 * 处理分析附件返回对应的json 内容
*
* @return * @return
*/ */
private String handleAnalyzedAttachment(String fileUrl, String fileSuffix) { private String handleAnalyzedAttachment(String fileUrl, String fileSuffix) {
@@ -706,12 +737,10 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
String resultStr = aiCommonToolsService.handleAnalyzedAttachment(contents); String resultStr = aiCommonToolsService.handleAnalyzedAttachment(contents);
log.info("返回的分析附件结果数据为:{}", resultStr); log.info("返回的分析附件结果数据为:{}", resultStr);
return resultStr; return resultStr;
}catch (Exception e){} } catch (Exception e) {
}
return ""; return "";
} }
} }