业务逻辑修改以及完善
This commit is contained in:
7
pom.xml
7
pom.xml
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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("*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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. **保留准确信息**:对于已从页面准确提取的信息(如jobTitle、companyName、location),请直接保留原值\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≥50000,salaryEnd≤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. val(standard):仅允许\"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)、data(HotakeRolesInfoDtoRes)、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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -87,23 +84,23 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
@Override
|
@Override
|
||||||
public HotakeCvInfo selectHotakeCvInfoById(Long id) {
|
public HotakeCvInfo selectHotakeCvInfoById(Long id) {
|
||||||
HotakeCvInfo cvInfo = hotakeCvInfoMapper.selectHotakeCvInfoById(id);
|
HotakeCvInfo cvInfo = hotakeCvInfoMapper.selectHotakeCvInfoById(id);
|
||||||
if(StrUtil.isNotEmpty(cvInfo.getCvTemplateJson())){
|
if (StrUtil.isNotEmpty(cvInfo.getCvTemplateJson())) {
|
||||||
HotakeCvInfoDto cvInfoDto = JSONUtil.toBean(cvInfo.getCvTemplateJson(),HotakeCvInfoDto.class);
|
HotakeCvInfoDto cvInfoDto = JSONUtil.toBean(cvInfo.getCvTemplateJson(), HotakeCvInfoDto.class);
|
||||||
cvInfo.setCvInfoDto(cvInfoDto);
|
cvInfo.setCvInfoDto(cvInfoDto);
|
||||||
}
|
}
|
||||||
if(StrUtil.isNotEmpty(cvInfo.getCvOptimizeJson())){
|
if (StrUtil.isNotEmpty(cvInfo.getCvOptimizeJson())) {
|
||||||
//解析优化简历
|
//解析优化简历
|
||||||
HotakeCvOptimizeDto cvOptimizeDto = JSONUtil.toBean(cvInfo.getCvOptimizeJson(),HotakeCvOptimizeDto.class);
|
HotakeCvOptimizeDto cvOptimizeDto = JSONUtil.toBean(cvInfo.getCvOptimizeJson(), HotakeCvOptimizeDto.class);
|
||||||
if(cvOptimizeDto != null){
|
if (cvOptimizeDto != null) {
|
||||||
cvInfo.setCvOptimizeDto(cvOptimizeDto);
|
cvInfo.setCvOptimizeDto(cvOptimizeDto);
|
||||||
if(cvOptimizeDto.getTextCorrections() != null && CollectionUtil.isNotEmpty(cvOptimizeDto.getTextCorrections().getFormattingIssues())){
|
if (cvOptimizeDto.getTextCorrections() != null && CollectionUtil.isNotEmpty(cvOptimizeDto.getTextCorrections().getFormattingIssues())) {
|
||||||
cvInfo.setTextCorrectionsNums(cvOptimizeDto.getTextCorrections().getFormattingIssues().size());
|
cvInfo.setTextCorrectionsNums(cvOptimizeDto.getTextCorrections().getFormattingIssues().size());
|
||||||
}else{
|
} else {
|
||||||
cvInfo.setTextCorrectionsNums(0);
|
cvInfo.setTextCorrectionsNums(0);
|
||||||
}
|
}
|
||||||
if(cvOptimizeDto.getLogicCorrections() != null && CollectionUtil.isNotEmpty(cvOptimizeDto.getLogicCorrections().getCareerProgressionDtos())){
|
if (cvOptimizeDto.getLogicCorrections() != null && CollectionUtil.isNotEmpty(cvOptimizeDto.getLogicCorrections().getCareerProgressionDtos())) {
|
||||||
cvInfo.setLogicCorrectionsNum(cvOptimizeDto.getTextCorrections().getFormattingIssues().size());
|
cvInfo.setLogicCorrectionsNum(cvOptimizeDto.getTextCorrections().getFormattingIssues().size());
|
||||||
}else{
|
} else {
|
||||||
cvInfo.setLogicCorrectionsNum(0);
|
cvInfo.setLogicCorrectionsNum(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +108,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
HotakeProblemBaseInfo query = new HotakeProblemBaseInfo();
|
HotakeProblemBaseInfo query = new HotakeProblemBaseInfo();
|
||||||
query.setCvId(id);
|
query.setCvId(id);
|
||||||
List<HotakeProblemBaseInfo> problemBaseInfoList = hotakeProblemBaseInfoService.selectHotakeProblemBaseInfoList(query);
|
List<HotakeProblemBaseInfo> problemBaseInfoList = hotakeProblemBaseInfoService.selectHotakeProblemBaseInfoList(query);
|
||||||
if(CollectionUtil.isNotEmpty(problemBaseInfoList)){
|
if (CollectionUtil.isNotEmpty(problemBaseInfoList)) {
|
||||||
cvInfo.setProblemBaseInfo(problemBaseInfoList.get(0));
|
cvInfo.setProblemBaseInfo(problemBaseInfoList.get(0));
|
||||||
}
|
}
|
||||||
return cvInfo;
|
return cvInfo;
|
||||||
@@ -127,9 +124,9 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
@Override
|
@Override
|
||||||
public List<HotakeCvInfo> selectHotakeCvInfoList(HotakeCvInfo hotakeCvInfo) {
|
public List<HotakeCvInfo> selectHotakeCvInfoList(HotakeCvInfo hotakeCvInfo) {
|
||||||
List<HotakeCvInfo> cvInfoList = hotakeCvInfoMapper.selectHotakeCvInfoList(hotakeCvInfo);
|
List<HotakeCvInfo> cvInfoList = hotakeCvInfoMapper.selectHotakeCvInfoList(hotakeCvInfo);
|
||||||
if(CollectionUtil.isNotEmpty(cvInfoList)){
|
if (CollectionUtil.isNotEmpty(cvInfoList)) {
|
||||||
for(HotakeCvInfo cvInfo : cvInfoList){
|
for (HotakeCvInfo cvInfo : cvInfoList) {
|
||||||
HotakeCvInfoDto cvInfoDto = JSONUtil.toBean(cvInfo.getCvTemplateJson(),HotakeCvInfoDto.class);
|
HotakeCvInfoDto cvInfoDto = JSONUtil.toBean(cvInfo.getCvTemplateJson(), HotakeCvInfoDto.class);
|
||||||
cvInfo.setCvInfoDto(cvInfoDto);
|
cvInfo.setCvInfoDto(cvInfoDto);
|
||||||
//返回问题记录
|
//返回问题记录
|
||||||
}
|
}
|
||||||
@@ -146,9 +143,9 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@Override
|
@Override
|
||||||
public HotakeCvInfo insertHotakeCvInfo(HotakeCvInfo hotakeCvInfo) {
|
public HotakeCvInfo insertHotakeCvInfo(HotakeCvInfo hotakeCvInfo) {
|
||||||
if("cv".equals(hotakeCvInfo.getCvFileType())){
|
if ("cv".equals(hotakeCvInfo.getCvFileType())) {
|
||||||
//删除原先的CV简历信息
|
//删除原先的CV简历信息
|
||||||
hotakeCvInfoMapper.deleteHotakeCvInfoByUserIdAndType(SecurityUtils.getUserId(),"cv");
|
hotakeCvInfoMapper.deleteHotakeCvInfoByUserIdAndType(SecurityUtils.getUserId(), "cv");
|
||||||
}
|
}
|
||||||
fill(FillTypeEnum.INSERT.getCode(), hotakeCvInfo);
|
fill(FillTypeEnum.INSERT.getCode(), hotakeCvInfo);
|
||||||
String fileSuffix = FileUtil.getSuffix(hotakeCvInfo.getCvUrl());
|
String fileSuffix = FileUtil.getSuffix(hotakeCvInfo.getCvUrl());
|
||||||
@@ -156,9 +153,9 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
fileSuffix = fileSuffix.toLowerCase();
|
fileSuffix = fileSuffix.toLowerCase();
|
||||||
}
|
}
|
||||||
hotakeCvInfo.setCvFileSuffix(fileSuffix);
|
hotakeCvInfo.setCvFileSuffix(fileSuffix);
|
||||||
if(!"cv".equals(hotakeCvInfo.getCvFileType())){
|
if (!"cv".equals(hotakeCvInfo.getCvFileType())) {
|
||||||
//对附件进行基础数据的解析
|
//对附件进行基础数据的解析
|
||||||
String resultStr = handleAnalyzedAttachment(hotakeCvInfo.getCvUrl(),fileSuffix);
|
String resultStr = handleAnalyzedAttachment(hotakeCvInfo.getCvUrl(), fileSuffix);
|
||||||
hotakeCvInfo.setAnalyzedAttachmentJson(resultStr);
|
hotakeCvInfo.setAnalyzedAttachmentJson(resultStr);
|
||||||
}
|
}
|
||||||
hotakeCvInfoMapper.insertHotakeCvInfo(hotakeCvInfo);
|
hotakeCvInfoMapper.insertHotakeCvInfo(hotakeCvInfo);
|
||||||
@@ -172,7 +169,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public HotakeProblemBaseInfo handleHotakeCvInfo(HotakeCvInfoDto cvInfoDto,Long cvId) {
|
public HotakeProblemBaseInfo handleHotakeCvInfo(HotakeCvInfoDto cvInfoDto, Long cvId) {
|
||||||
log.info("处理简历信息");
|
log.info("处理简历信息");
|
||||||
HotakeProblemBaseInfo problemBaseInfo = new HotakeProblemBaseInfo();
|
HotakeProblemBaseInfo problemBaseInfo = new HotakeProblemBaseInfo();
|
||||||
try {
|
try {
|
||||||
@@ -203,7 +200,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
}
|
}
|
||||||
//生成预设问题记录
|
//生成预设问题记录
|
||||||
//先删除该用户的预设问题
|
//先删除该用户的预设问题
|
||||||
hotakeProblemBaseInfoService.deleteHotakeProblemBaseInfoByUserId(SecurityUtils.getUserId(),cvId);
|
hotakeProblemBaseInfoService.deleteHotakeProblemBaseInfoByUserId(SecurityUtils.getUserId(), cvId);
|
||||||
problemBaseInfo.setUserId(SecurityUtils.getUserId());
|
problemBaseInfo.setUserId(SecurityUtils.getUserId());
|
||||||
problemBaseInfo.setContents(resultMsg);
|
problemBaseInfo.setContents(resultMsg);
|
||||||
problemBaseInfo.setQuestionNums(questionNums);
|
problemBaseInfo.setQuestionNums(questionNums);
|
||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
@@ -290,10 +287,10 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
.object(cvInfo.getCvUrl())
|
.object(cvInfo.getCvUrl())
|
||||||
.build());
|
.build());
|
||||||
String contents = FileContentUtil.readFileContent(inputStream, cvInfo.getCvFileSuffix());
|
String contents = FileContentUtil.readFileContent(inputStream, cvInfo.getCvFileSuffix());
|
||||||
log.info("简历信息:{}",contents);
|
log.info("简历信息:{}", contents);
|
||||||
//验证文件内容是否改变,如果未改变不进行模型解析直接返回原有结果
|
//验证文件内容是否改变,如果未改变不进行模型解析直接返回原有结果
|
||||||
String md5Hash = MD5.create().digestHex16(contents);
|
String md5Hash = MD5.create().digestHex16(contents);
|
||||||
if(StrUtil.isNotEmpty(md5Hash) && md5Hash.equals(cvInfo.getCvMd5())){
|
if (StrUtil.isNotEmpty(md5Hash) && md5Hash.equals(cvInfo.getCvMd5())) {
|
||||||
//直接返回简历结果
|
//直接返回简历结果
|
||||||
return cvInfo;
|
return cvInfo;
|
||||||
}
|
}
|
||||||
@@ -302,21 +299,21 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
HotakeCvInfoDto cvInfoDto = handleAnalysisCvInfo(contents);
|
HotakeCvInfoDto cvInfoDto = handleAnalysisCvInfo(contents);
|
||||||
cvInfo.setCvInfoDto(cvInfoDto);
|
cvInfo.setCvInfoDto(cvInfoDto);
|
||||||
//对简历数据进行处理生成相应的题库数据
|
//对简历数据进行处理生成相应的题库数据
|
||||||
HotakeProblemBaseInfo problemBaseInfo = handleHotakeCvInfo(cvInfoDto,hotakeCvInfo.getId());
|
HotakeProblemBaseInfo problemBaseInfo = handleHotakeCvInfo(cvInfoDto, hotakeCvInfo.getId());
|
||||||
cvInfo.setProblemBaseInfo(problemBaseInfo);
|
cvInfo.setProblemBaseInfo(problemBaseInfo);
|
||||||
//解析优化简历
|
//解析优化简历
|
||||||
HotakeCvOptimizeDto cvOptimizeDto = aiCommonToolsService.getResumeAnalysisOptimizer(contents);
|
HotakeCvOptimizeDto cvOptimizeDto = aiCommonToolsService.getResumeAnalysisOptimizer(contents);
|
||||||
|
|
||||||
if(cvOptimizeDto != null){
|
if (cvOptimizeDto != null) {
|
||||||
cvInfo.setCvOptimizeJson(JSONUtil.toJsonStr(cvOptimizeDto));
|
cvInfo.setCvOptimizeJson(JSONUtil.toJsonStr(cvOptimizeDto));
|
||||||
if(cvOptimizeDto.getTextCorrections() != null && CollectionUtil.isNotEmpty(cvOptimizeDto.getTextCorrections().getFormattingIssues())){
|
if (cvOptimizeDto.getTextCorrections() != null && CollectionUtil.isNotEmpty(cvOptimizeDto.getTextCorrections().getFormattingIssues())) {
|
||||||
cvInfo.setTextCorrectionsNums(cvOptimizeDto.getTextCorrections().getFormattingIssues().size());
|
cvInfo.setTextCorrectionsNums(cvOptimizeDto.getTextCorrections().getFormattingIssues().size());
|
||||||
}else{
|
} else {
|
||||||
cvInfo.setTextCorrectionsNums(0);
|
cvInfo.setTextCorrectionsNums(0);
|
||||||
}
|
}
|
||||||
if(cvOptimizeDto.getLogicCorrections() != null && CollectionUtil.isNotEmpty(cvOptimizeDto.getLogicCorrections().getCareerProgressionDtos())){
|
if (cvOptimizeDto.getLogicCorrections() != null && CollectionUtil.isNotEmpty(cvOptimizeDto.getLogicCorrections().getCareerProgressionDtos())) {
|
||||||
cvInfo.setLogicCorrectionsNum(cvOptimizeDto.getTextCorrections().getFormattingIssues().size());
|
cvInfo.setLogicCorrectionsNum(cvOptimizeDto.getTextCorrections().getFormattingIssues().size());
|
||||||
}else{
|
} else {
|
||||||
cvInfo.setLogicCorrectionsNum(0);
|
cvInfo.setLogicCorrectionsNum(0);
|
||||||
}
|
}
|
||||||
cvInfo.setCvOptimizeDto(cvOptimizeDto);
|
cvInfo.setCvOptimizeDto(cvOptimizeDto);
|
||||||
@@ -352,6 +349,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据其他的附件信息生成个人简历
|
* 根据其他的附件信息生成个人简历
|
||||||
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -365,46 +363,76 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
String basicInformation = "";
|
String basicInformation = "";
|
||||||
//其他的附件信息集合
|
//其他的附件信息集合
|
||||||
String attachmentContent = "";
|
String attachmentContent = "";
|
||||||
if(CollectionUtil.isNotEmpty(cvInfoList)){
|
if (CollectionUtil.isNotEmpty(cvInfoList)) {
|
||||||
List<HotakeCvInfo> cvInfos = cvInfoList.stream().filter(e->"letter".equals(e.getCvFileType())).toList();
|
List<HotakeCvInfo> cvInfos = cvInfoList.stream().filter(e -> "letter".equals(e.getCvFileType())).toList();
|
||||||
if(CollectionUtil.isNotEmpty(cvInfos)){
|
if (CollectionUtil.isNotEmpty(cvInfos)) {
|
||||||
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) {
|
||||||
if(StrUtil.isNotEmpty(hotakeCvInfo.getAnalyzedAttachmentJson())){
|
if (StrUtil.isNotEmpty(hotakeCvInfo.getAnalyzedAttachmentJson())) {
|
||||||
Map map = JSONUtil.toBean(hotakeCvInfo.getAnalyzedAttachmentJson(), Map.class);
|
Map map = JSONUtil.toBean(hotakeCvInfo.getAnalyzedAttachmentJson(), Map.class);
|
||||||
attachmentContentList.add(map);
|
attachmentContentList.add(map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(CollectionUtil.isNotEmpty(attachmentContentList)){
|
if (CollectionUtil.isNotEmpty(attachmentContentList)) {
|
||||||
attachmentContent = JSONUtil.toJsonStr(attachmentContentList);
|
attachmentContent = JSONUtil.toJsonStr(attachmentContentList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String cvData = aiCommonToolsService.handleAttachmentResultMerging(basicInformation,attachmentContent);
|
String cvData = aiCommonToolsService.handleAttachmentResultMerging(basicInformation, attachmentContent);
|
||||||
//进行简历数据组合
|
//进行简历数据组合
|
||||||
Map dataMap = JSONUtil.toBean(cvData, Map.class);
|
Map dataMap = JSONUtil.toBean(cvData, Map.class);
|
||||||
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) {
|
||||||
Map contactDetailsMap = (Map)personalInfoMap.get("contact_details");
|
cvInfoDto.setName(StringUtils.getObjectStr(personalInfoMap.get("full_name")));
|
||||||
cvInfoDto.setPhone(contactDetailsMap.get("phone").toString());
|
Map contactDetailsMap = (Map) personalInfoMap.get("contact_details");
|
||||||
cvInfoDto.setEmail(contactDetailsMap.get("email").toString());
|
if (contactDetailsMap != null) {
|
||||||
cvInfoDto.setLocation(contactDetailsMap.get("address").toString());
|
cvInfoDto.setPhone(StringUtils.getObjectStr(contactDetailsMap.get("phone")));
|
||||||
//个人介绍
|
cvInfoDto.setEmail(StringUtils.getObjectStr(contactDetailsMap.get("email")));
|
||||||
cvInfoDto.setAbout(dataMap.get("professional_summary").toString());
|
cvInfoDto.setLocation(StringUtils.getObjectStr(contactDetailsMap.get("address")));
|
||||||
|
} 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<>();
|
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)){
|
if(CollectionUtil.isNotEmpty(skillsList)){
|
||||||
|
//技能工具
|
||||||
|
List<VcSkillsToolsDto> skillsTools = new ArrayList<>();
|
||||||
|
if (CollectionUtil.isNotEmpty(skillsList)) {
|
||||||
for (String skill : skillsList) {
|
for (String skill : skillsList) {
|
||||||
VcSkillsToolsDto toolsDto = new VcSkillsToolsDto();
|
VcSkillsToolsDto toolsDto = new VcSkillsToolsDto();
|
||||||
toolsDto.setContent(skill);
|
toolsDto.setContent(skill);
|
||||||
@@ -412,26 +440,27 @@ 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");
|
||||||
if (CollectionUtil.isNotEmpty(workExperienceMapList)) {
|
if (CollectionUtil.isNotEmpty(workExperienceMapList)) {
|
||||||
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)) {
|
||||||
for (String responsibility : responsibilitiesList) {
|
for (String responsibility : responsibilitiesList) {
|
||||||
VcExperienceDescriptionDto descriptionDto = new VcExperienceDescriptionDto();
|
VcExperienceDescriptionDto descriptionDto = new VcExperienceDescriptionDto();
|
||||||
descriptionDto.setContent(responsibility);
|
descriptionDto.setContent(responsibility);
|
||||||
@@ -445,17 +474,17 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
}
|
}
|
||||||
//教育经历
|
//教育经历
|
||||||
List<VcEducationDto> educationList = new ArrayList<>();
|
List<VcEducationDto> educationList = new ArrayList<>();
|
||||||
List<Map> educationMapList = (List<Map>)dataMap.get("education");
|
List<Map> educationMapList = (List<Map>) dataMap.get("education");
|
||||||
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]);
|
||||||
if(times.length > 1){
|
if (times.length > 1) {
|
||||||
educationDto.setDurationEnd(times[1]);
|
educationDto.setDurationEnd(times[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -473,7 +502,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
cvInfo.setStatus("1");
|
cvInfo.setStatus("1");
|
||||||
//生成简历PDF数据
|
//生成简历PDF数据
|
||||||
String markdown = aiCommonToolsService.handleGenerateMarkdown(cvData);
|
String markdown = aiCommonToolsService.handleGenerateMarkdown(cvData);
|
||||||
markdown = markdown.replaceAll("markdown","");
|
markdown = markdown.replaceAll("markdown", "");
|
||||||
try {
|
try {
|
||||||
String html = MarkdownUtil.markdownToHtml(markdown);
|
String html = MarkdownUtil.markdownToHtml(markdown);
|
||||||
// 可注入 CSS
|
// 可注入 CSS
|
||||||
@@ -501,14 +530,15 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
MultipartFileUtil.fileToMultipartFile(pdf);
|
MultipartFileUtil.fileToMultipartFile(pdf);
|
||||||
HotakeSysFileVo fileVo = new HotakeSysFileVo();
|
HotakeSysFileVo fileVo = new HotakeSysFileVo();
|
||||||
fileVo.setMinioBucketName("cv-fs");
|
fileVo.setMinioBucketName("cv-fs");
|
||||||
HotakeSysFile sysFile = sysFileService.insertHotakeSysFile(multipartFile,fileVo);
|
HotakeSysFile sysFile = sysFileService.insertHotakeSysFile(multipartFile, fileVo);
|
||||||
cvInfo.setFileSizeShow(sysFile.getFileSizeShow());
|
cvInfo.setFileSizeShow(sysFile.getFileSizeShow());
|
||||||
cvInfo.setCvUrl(sysFile.getStoragePath());
|
cvInfo.setCvUrl(sysFile.getStoragePath());
|
||||||
//创建简历对象数据
|
//创建简历对象数据
|
||||||
insertHotakeCvInfo(cvInfo);
|
insertHotakeCvInfo(cvInfo);
|
||||||
//保存简历PDF附件数据
|
//保存简历PDF附件数据
|
||||||
// log.info("Markdown数据为:{}",markdown);
|
// log.info("Markdown数据为:{}",markdown);
|
||||||
}catch (Exception e){}
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
return cvInfo;
|
return cvInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,81 +552,81 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
private HotakeCvInfoDto handleAnalysisCvInfo(String contents) {
|
private HotakeCvInfoDto handleAnalysisCvInfo(String contents) {
|
||||||
log.info("开始简历解析");
|
log.info("开始简历解析");
|
||||||
try {
|
try {
|
||||||
List<Map<String,String>> list = new LinkedList();
|
List<Map<String, String>> list = new LinkedList();
|
||||||
Map<String,String> entity = new HashMap<>();
|
Map<String, String> entity = new HashMap<>();
|
||||||
entity.put("role","user");
|
entity.put("role", "user");
|
||||||
entity.put("content","从下面提供的文本中提取所有能识别到的简历信息,只提取原文中存在的内容,不要补充、不推测、不总结。需要提取的字段包括:name(姓名或人名)、phone(电话号码)、email(电子邮件地址)、experienceYear(根据工作经验计算出来的工作年限)、position(岗位或者简历中自己期望的职位等)、location(地点或者地址、家庭住址)、links(所有链接地址)、currentWork(当前工作公司)、about(关于我/自我介绍)、skills_tools(关键资格(许可证、注册/会员资格、认证))、languages(语言能力,主要就是Languages下面的语言)、experience(工作经历,除了title、company、location、durationStart、durationEnd,其他的都放到description里面,并且description里面要根据换行符分成不同的content),日期要拆分成开始时间(durationStart)和结束时间(durationEnd))、education(教育经历,日期要拆分成开始时间(durationStart)和结束时间(durationEnd))。请将提取结果以结构化 JSON 格式返回,格式如下:{ \\\"name\\\": \\\"\\\", \\\"phone\\\": \\\"\\\", \\\"currentWork\\\": \\\"\\\", \\\"position\\\": \\\"\\\", \\\"location\\\": \\\"\\\", \\\"email\\\": \\\"\\\", \\\"experienceYear\\\": \\\"\\\", \\\"links\\\": [{\\\"content\\\":\\\"\\\"}], \\\"about\\\": \\\"\\\", \\\"skillsTools\\\": [{\\\"content\\\":\\\"\\\"}], \\\"languages\\\": [{\\\"content\\\":\\\"\\\"}], \\\"experience\\\": [{\\\"title\\\": \\\"\\\", \\\"company\\\": \\\"\\\",\\\"location\\\": \\\"\\\",\\\"durationStart\\\": \\\"\\\",\\\"durationEnd\\\": \\\"\\\",\\\"description\\\": [{\\\"content\\\":\\\"\\\"}]}], \\\"education\\\": [{\\\"degree\\\": \\\"\\\",\\\"institution\\\": \\\"\\\",\\\"durationStart\\\": \\\"\\\",\\\"durationEnd\\\": \\\"\\\"}] }。字段不存在则返回 null 或空数组。只返回标准可解析的 JSON结构 ,不要多余的```json等信息,不要解释说明,不要改写内容。以下为待处理文本:"+contents);
|
entity.put("content", "从下面提供的文本中提取所有能识别到的简历信息,只提取原文中存在的内容,不要补充、不推测、不总结。需要提取的字段包括:name(姓名或人名)、phone(电话号码)、email(电子邮件地址)、experienceYear(根据工作经验计算出来的工作年限)、position(岗位或者简历中自己期望的职位等)、location(地点或者地址、家庭住址)、links(所有链接地址)、currentWork(当前工作公司)、about(关于我/自我介绍)、skills_tools(关键资格(许可证、注册/会员资格、认证))、languages(语言能力,主要就是Languages下面的语言)、experience(工作经历,除了title、company、location、durationStart、durationEnd,其他的都放到description里面,并且description里面要根据换行符分成不同的content),日期要拆分成开始时间(durationStart)和结束时间(durationEnd))、education(教育经历,日期要拆分成开始时间(durationStart)和结束时间(durationEnd))。请将提取结果以结构化 JSON 格式返回,格式如下:{ \\\"name\\\": \\\"\\\", \\\"phone\\\": \\\"\\\", \\\"currentWork\\\": \\\"\\\", \\\"position\\\": \\\"\\\", \\\"location\\\": \\\"\\\", \\\"email\\\": \\\"\\\", \\\"experienceYear\\\": \\\"\\\", \\\"links\\\": [{\\\"content\\\":\\\"\\\"}], \\\"about\\\": \\\"\\\", \\\"skillsTools\\\": [{\\\"content\\\":\\\"\\\"}], \\\"languages\\\": [{\\\"content\\\":\\\"\\\"}], \\\"experience\\\": [{\\\"title\\\": \\\"\\\", \\\"company\\\": \\\"\\\",\\\"location\\\": \\\"\\\",\\\"durationStart\\\": \\\"\\\",\\\"durationEnd\\\": \\\"\\\",\\\"description\\\": [{\\\"content\\\":\\\"\\\"}]}], \\\"education\\\": [{\\\"degree\\\": \\\"\\\",\\\"institution\\\": \\\"\\\",\\\"durationStart\\\": \\\"\\\",\\\"durationEnd\\\": \\\"\\\"}] }。字段不存在则返回 null 或空数组。只返回标准可解析的 JSON结构 ,不要多余的```json等信息,不要解释说明,不要改写内容。以下为待处理文本:" + contents);
|
||||||
//根据AI做
|
//根据AI做
|
||||||
list.add(entity);
|
list.add(entity);
|
||||||
String resultCv = chatGPTClient.handleAiChat(JSONUtil.toJsonStr(list), "JX");
|
String resultCv = chatGPTClient.handleAiChat(JSONUtil.toJsonStr(list), "JX");
|
||||||
log.info("开始返回简历解析结果:{}", resultCv);
|
log.info("开始返回简历解析结果:{}", resultCv);
|
||||||
HotakeCvInfoDto cvInfoDto = JSONUtil.toBean(resultCv, HotakeCvInfoDto.class);
|
HotakeCvInfoDto cvInfoDto = JSONUtil.toBean(resultCv, HotakeCvInfoDto.class);
|
||||||
//数据添加默认值
|
//数据添加默认值
|
||||||
if(cvInfoDto != null){
|
if (cvInfoDto != null) {
|
||||||
if(StrUtil.isEmpty(cvInfoDto.getName())){
|
if (StrUtil.isEmpty(cvInfoDto.getName())) {
|
||||||
cvInfoDto.setName("-");
|
cvInfoDto.setName("-");
|
||||||
}
|
}
|
||||||
if(StrUtil.isEmpty(cvInfoDto.getPhone())){
|
if (StrUtil.isEmpty(cvInfoDto.getPhone())) {
|
||||||
cvInfoDto.setPhone("-");
|
cvInfoDto.setPhone("-");
|
||||||
}
|
}
|
||||||
if(StrUtil.isEmpty(cvInfoDto.getEmail())){
|
if (StrUtil.isEmpty(cvInfoDto.getEmail())) {
|
||||||
cvInfoDto.setEmail("-");
|
cvInfoDto.setEmail("-");
|
||||||
}
|
}
|
||||||
if(StrUtil.isEmpty(cvInfoDto.getPosition())){
|
if (StrUtil.isEmpty(cvInfoDto.getPosition())) {
|
||||||
cvInfoDto.setPosition("-");
|
cvInfoDto.setPosition("-");
|
||||||
}
|
}
|
||||||
if(StrUtil.isEmpty(cvInfoDto.getLocation())){
|
if (StrUtil.isEmpty(cvInfoDto.getLocation())) {
|
||||||
cvInfoDto.setLocation("-");
|
cvInfoDto.setLocation("-");
|
||||||
}
|
}
|
||||||
if(StrUtil.isEmpty(cvInfoDto.getAbout())){
|
if (StrUtil.isEmpty(cvInfoDto.getAbout())) {
|
||||||
cvInfoDto.setAbout("-");
|
cvInfoDto.setAbout("-");
|
||||||
}
|
}
|
||||||
if (StrUtil.isEmpty(cvInfoDto.getCurrentWork())){
|
if (StrUtil.isEmpty(cvInfoDto.getCurrentWork())) {
|
||||||
cvInfoDto.setCurrentWork("-");
|
cvInfoDto.setCurrentWork("-");
|
||||||
}
|
}
|
||||||
if(CollectionUtil.isNotEmpty(cvInfoDto.getSkillsTools())){
|
if (CollectionUtil.isNotEmpty(cvInfoDto.getSkillsTools())) {
|
||||||
for (VcSkillsToolsDto toolsDto :cvInfoDto.getSkillsTools()) {
|
for (VcSkillsToolsDto toolsDto : cvInfoDto.getSkillsTools()) {
|
||||||
if(StrUtil.isEmpty(toolsDto.getContent())){
|
if (StrUtil.isEmpty(toolsDto.getContent())) {
|
||||||
toolsDto.setContent("-");
|
toolsDto.setContent("-");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(CollectionUtil.isNotEmpty(cvInfoDto.getLinks())){
|
if (CollectionUtil.isNotEmpty(cvInfoDto.getLinks())) {
|
||||||
for (VcLinksDto linksDto :cvInfoDto.getLinks()) {
|
for (VcLinksDto linksDto : cvInfoDto.getLinks()) {
|
||||||
if(StrUtil.isEmpty(linksDto.getContent())){
|
if (StrUtil.isEmpty(linksDto.getContent())) {
|
||||||
linksDto.setContent("-");
|
linksDto.setContent("-");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(CollectionUtil.isNotEmpty(cvInfoDto.getLanguages())){
|
if (CollectionUtil.isNotEmpty(cvInfoDto.getLanguages())) {
|
||||||
for (VcLanguagesDto languagesDto :cvInfoDto.getLanguages()) {
|
for (VcLanguagesDto languagesDto : cvInfoDto.getLanguages()) {
|
||||||
if(StrUtil.isEmpty(languagesDto.getContent())){
|
if (StrUtil.isEmpty(languagesDto.getContent())) {
|
||||||
languagesDto.setContent("-");
|
languagesDto.setContent("-");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(CollectionUtil.isNotEmpty(cvInfoDto.getExperience())){
|
if (CollectionUtil.isNotEmpty(cvInfoDto.getExperience())) {
|
||||||
for (VcExperienceDto experienceDto :cvInfoDto.getExperience()) {
|
for (VcExperienceDto experienceDto : cvInfoDto.getExperience()) {
|
||||||
if (StrUtil.isEmpty(experienceDto.getTitle())){
|
if (StrUtil.isEmpty(experienceDto.getTitle())) {
|
||||||
experienceDto.setTitle("-");
|
experienceDto.setTitle("-");
|
||||||
}
|
}
|
||||||
if(StrUtil.isEmpty(experienceDto.getCompany())){
|
if (StrUtil.isEmpty(experienceDto.getCompany())) {
|
||||||
experienceDto.setCompany("-");
|
experienceDto.setCompany("-");
|
||||||
}
|
}
|
||||||
if(StrUtil.isEmpty(experienceDto.getLocation())){
|
if (StrUtil.isEmpty(experienceDto.getLocation())) {
|
||||||
experienceDto.setLocation("-");
|
experienceDto.setLocation("-");
|
||||||
}
|
}
|
||||||
if(StrUtil.isEmpty(experienceDto.getDurationStart())){
|
if (StrUtil.isEmpty(experienceDto.getDurationStart())) {
|
||||||
experienceDto.setDurationStart("-");
|
experienceDto.setDurationStart("-");
|
||||||
}
|
}
|
||||||
if(StrUtil.isEmpty(experienceDto.getDurationEnd())){
|
if (StrUtil.isEmpty(experienceDto.getDurationEnd())) {
|
||||||
experienceDto.setDurationEnd("-");
|
experienceDto.setDurationEnd("-");
|
||||||
}
|
}
|
||||||
if (CollectionUtil.isNotEmpty(experienceDto.getDescription())){
|
if (CollectionUtil.isNotEmpty(experienceDto.getDescription())) {
|
||||||
for(VcExperienceDescriptionDto descriptionDto :experienceDto.getDescription()){
|
for (VcExperienceDescriptionDto descriptionDto : experienceDto.getDescription()) {
|
||||||
if(StrUtil.isEmpty(descriptionDto.getContent())){
|
if (StrUtil.isEmpty(descriptionDto.getContent())) {
|
||||||
descriptionDto.setContent("-");
|
descriptionDto.setContent("-");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,31 +635,31 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(CollectionUtil.isNotEmpty(cvInfoDto.getEducation())){
|
if (CollectionUtil.isNotEmpty(cvInfoDto.getEducation())) {
|
||||||
for (VcEducationDto educationDto :cvInfoDto.getEducation()) {
|
for (VcEducationDto educationDto : cvInfoDto.getEducation()) {
|
||||||
if (StrUtil.isEmpty(educationDto.getDegree())){
|
if (StrUtil.isEmpty(educationDto.getDegree())) {
|
||||||
educationDto.setDegree("-");
|
educationDto.setDegree("-");
|
||||||
}
|
}
|
||||||
if(StrUtil.isEmpty(educationDto.getInstitution())){
|
if (StrUtil.isEmpty(educationDto.getInstitution())) {
|
||||||
educationDto.setInstitution("-");
|
educationDto.setInstitution("-");
|
||||||
}
|
}
|
||||||
if(StrUtil.isEmpty(educationDto.getDurationStart())){
|
if (StrUtil.isEmpty(educationDto.getDurationStart())) {
|
||||||
educationDto.setDurationStart("-");
|
educationDto.setDurationStart("-");
|
||||||
}
|
}
|
||||||
if(StrUtil.isEmpty(educationDto.getDurationEnd())){
|
if (StrUtil.isEmpty(educationDto.getDurationEnd())) {
|
||||||
educationDto.setDurationEnd("-");
|
educationDto.setDurationEnd("-");
|
||||||
}
|
}
|
||||||
if(educationDto.getCertificate() != null){
|
if (educationDto.getCertificate() != null) {
|
||||||
if(StrUtil.isEmpty(educationDto.getCertificate().getFileName())){
|
if (StrUtil.isEmpty(educationDto.getCertificate().getFileName())) {
|
||||||
educationDto.getCertificate().setFileName("-");
|
educationDto.getCertificate().setFileName("-");
|
||||||
}
|
}
|
||||||
if (StrUtil.isEmpty(educationDto.getCertificate().getFileSuffix())){
|
if (StrUtil.isEmpty(educationDto.getCertificate().getFileSuffix())) {
|
||||||
educationDto.getCertificate().setFileSuffix("-");
|
educationDto.getCertificate().setFileSuffix("-");
|
||||||
}
|
}
|
||||||
if (StrUtil.isEmpty(educationDto.getCertificate().getFileUrl())){
|
if (StrUtil.isEmpty(educationDto.getCertificate().getFileUrl())) {
|
||||||
educationDto.getCertificate().setFileUrl("-");
|
educationDto.getCertificate().setFileUrl("-");
|
||||||
}
|
}
|
||||||
if (StrUtil.isEmpty(educationDto.getCertificate().getFileSizeShow())){
|
if (StrUtil.isEmpty(educationDto.getCertificate().getFileSizeShow())) {
|
||||||
educationDto.getCertificate().setFileSizeShow("-");
|
educationDto.getCertificate().setFileSizeShow("-");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -680,21 +710,22 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void main(String[] args){
|
public static void main(String[] args) {
|
||||||
String str = "Score: 4.8/5\n" +
|
String str = "Score: 4.8/5\n" +
|
||||||
"Recommendation: Highly recommended candidate - strong match for the role\n" +
|
"Recommendation: Highly recommended candidate - strong match for the role\n" +
|
||||||
"Strengths: Extensive relevant experience, Professional certifications, Strong skill set alignment\n" +
|
"Strengths: Extensive relevant experience, Professional certifications, Strong skill set alignment\n" +
|
||||||
"Concerns: -";
|
"Concerns: -";
|
||||||
|
|
||||||
String[] strs = str.split("\n");
|
String[] strs = str.split("\n");
|
||||||
System.out.println(strs[0].replaceAll("Score:","").trim().split("/")[0]);
|
System.out.println(strs[0].replaceAll("Score:", "").trim().split("/")[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理分析附件返回对应的json 内容
|
* 处理分析附件返回对应的json 内容
|
||||||
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private String handleAnalyzedAttachment(String fileUrl,String fileSuffix){
|
private String handleAnalyzedAttachment(String fileUrl, String fileSuffix) {
|
||||||
log.info("开始处理附件,对附件进行解析");
|
log.info("开始处理附件,对附件进行解析");
|
||||||
try {
|
try {
|
||||||
InputStream inputStream = minioClient.getObject(
|
InputStream inputStream = minioClient.getObject(
|
||||||
@@ -704,14 +735,12 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
|||||||
.build());
|
.build());
|
||||||
String contents = FileContentUtil.readFileContent(inputStream, fileSuffix);
|
String contents = FileContentUtil.readFileContent(inputStream, fileSuffix);
|
||||||
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 "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user