添加简历识别模块—ocr缺少重新打包
This commit is contained in:
@@ -221,11 +221,11 @@ file:
|
|||||||
|
|
||||||
ocr:
|
ocr:
|
||||||
ocr_url: http://127.0.0.1:9001/ocr
|
ocr_url: http://127.0.0.1:9001/ocr
|
||||||
ocr_mutipart: https://qd.zhaopinzao8dian.com/ocr-api/ocr
|
# ocr_mutipart: https://qd.zhaopinzao8dian.com/ocr-api/ocr
|
||||||
# ocr_mutipart: http://10.98.80.141:9000/ocr
|
ocr_mutipart: http://10.98.80.141:9000/ocr
|
||||||
ocr_llm_url: http://39.98.44.136:6016/inner-ai/aicoapi/gateway/v2/chatbot/api_run/1763386387_d4c07131-a047-4c0d-9623-7e3c3a45bd7e
|
ocr_llm_url: http://39.98.44.136:6016/inner-ai/aicoapi/gateway/v2/chatbot/api_run/1763386387_d4c07131-a047-4c0d-9623-7e3c3a45bd7e
|
||||||
ocr_llm_apiKey: NfzPnFRtogHlYCAh2hHIB7ra5EsrSQEM
|
ocr_llm_apiKey: NfzPnFRtogHlYCAh2hHIB7ra5EsrSQEM
|
||||||
ocr_llm_chatUrl: http://39.98.44.136:6016/v1/chat/completions
|
ocr_llm_chatUrl: http://10.98.76.104:1025/v1/chat/completions
|
||||||
|
|
||||||
cipher-security:
|
cipher-security:
|
||||||
socket: 39.102.146.78:11028
|
socket: 39.102.146.78:11028
|
||||||
|
|||||||
@@ -65,4 +65,6 @@ public interface IJobTitleService
|
|||||||
List<JobTitle> levelOne();
|
List<JobTitle> levelOne();
|
||||||
|
|
||||||
Set<String> jobTitleNameSets();
|
Set<String> jobTitleNameSets();
|
||||||
|
|
||||||
|
String queryNameToJobIds(List<String> jobNames);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
package com.ruoyi.cms.service.impl;
|
package com.ruoyi.cms.service.impl;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.ruoyi.cms.domain.BussinessDictData;
|
||||||
import com.ruoyi.cms.domain.JobApply;
|
import com.ruoyi.cms.domain.JobApply;
|
||||||
import com.ruoyi.cms.domain.UserInfoDetail;
|
import com.ruoyi.cms.domain.UserInfoDetail;
|
||||||
|
import com.ruoyi.cms.service.IBussinessDictDataService;
|
||||||
|
import com.ruoyi.cms.service.IBussinessDictTypeService;
|
||||||
|
import com.ruoyi.cms.service.IJobTitleService;
|
||||||
import com.ruoyi.cms.util.AppUserFieldCustomCopy;
|
import com.ruoyi.cms.util.AppUserFieldCustomCopy;
|
||||||
import com.ruoyi.cms.util.DictUtils;
|
import com.ruoyi.cms.util.DictUtils;
|
||||||
import com.ruoyi.cms.util.encrypt.QuickValidUtils;
|
import com.ruoyi.cms.util.encrypt.QuickValidUtils;
|
||||||
@@ -30,6 +36,7 @@ import com.ruoyi.common.utils.SecurityUtils;
|
|||||||
import com.ruoyi.common.utils.SiteSecurityUtils;
|
import com.ruoyi.common.utils.SiteSecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.common.utils.bean.BeanUtils;
|
import com.ruoyi.common.utils.bean.BeanUtils;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
@@ -71,6 +78,12 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
|
|||||||
private FileMapper fileMapper;
|
private FileMapper fileMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private JobApplyMapper jobApplyMapper;
|
private JobApplyMapper jobApplyMapper;
|
||||||
|
@Autowired
|
||||||
|
private IBussinessDictTypeService iBussinessDictTypeService;
|
||||||
|
@Autowired
|
||||||
|
private IBussinessDictDataService iBussinessDictDataService;
|
||||||
|
@Autowired
|
||||||
|
private IJobTitleService iJobTitleService;
|
||||||
|
|
||||||
@Value("${ocr.ocr_mutipart}")
|
@Value("${ocr.ocr_mutipart}")
|
||||||
private String ocrMutipartUrl;
|
private String ocrMutipartUrl;
|
||||||
@@ -80,10 +93,16 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
|
|||||||
@Value("${ocr.ocr_llm_apiKey}")
|
@Value("${ocr.ocr_llm_apiKey}")
|
||||||
private String llmApiKey;
|
private String llmApiKey;
|
||||||
|
|
||||||
|
@Value("${ocr.ocr_llm_chatUrl}")
|
||||||
|
private String llmChatUrl;
|
||||||
|
|
||||||
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private OauthClient oauthClient;
|
private OauthClient oauthClient;
|
||||||
|
@Autowired
|
||||||
|
private ExecutorService sseLlmExecutor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询APP用户
|
* 查询APP用户
|
||||||
@@ -680,7 +699,7 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
|
|||||||
public AppUser getUserInfo() {
|
public AppUser getUserInfo() {
|
||||||
//查询用户信息
|
//查询用户信息
|
||||||
LoginUser loginUser=SecurityUtils.getLoginUser();
|
LoginUser loginUser=SecurityUtils.getLoginUser();
|
||||||
System.out.println("loginUser========================"+JSON.toJSONString(loginUser));
|
System.out.println("loginUser========================"+ JSON.toJSONString(loginUser));
|
||||||
SysUser sysUser=loginUser.getUser();
|
SysUser sysUser=loginUser.getUser();
|
||||||
System.out.println("sysUser========================"+JSON.toJSONString(sysUser));
|
System.out.println("sysUser========================"+JSON.toJSONString(sysUser));
|
||||||
AppUser appUser=selectAppuserByIdcard(sysUser.getIdCard());
|
AppUser appUser=selectAppuserByIdcard(sysUser.getIdCard());
|
||||||
@@ -861,7 +880,8 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- 第二步:调用大模型进行简历分析 ---
|
// --- 第二步:调用大模型进行简历分析 ---
|
||||||
JSONObject llmResult = callLlmService(ocrText,userId);
|
//JSONObject llmResult = callLlmService(ocrText,userId);
|
||||||
|
JSONObject llmResult = backChatService(ocrText,userId);
|
||||||
if(llmResult.containsKey("error")){
|
if(llmResult.containsKey("error")){
|
||||||
return AjaxResult.error(llmResult.getString("error"));
|
return AjaxResult.error(llmResult.getString("error"));
|
||||||
}
|
}
|
||||||
@@ -979,6 +999,104 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JSONObject backChatService(String contextText,Long userId) {
|
||||||
|
// 1. 构造长超时RestTemplate,适配长文本OCR解析
|
||||||
|
org.springframework.http.client.SimpleClientHttpRequestFactory factory = new org.springframework.http.client.SimpleClientHttpRequestFactory();
|
||||||
|
factory.setConnectTimeout(10000);
|
||||||
|
// OCR长文本推理时间久,设120秒超时
|
||||||
|
factory.setReadTimeout(120000);
|
||||||
|
RestTemplate longTimeoutRestTemplate = new RestTemplate(factory);
|
||||||
|
|
||||||
|
// 2. 组装OpenAI标准请求体,关闭流式
|
||||||
|
JSONObject requestBody = new JSONObject();
|
||||||
|
requestBody.put("model", "qwen3");
|
||||||
|
requestBody.put("stream", false);
|
||||||
|
requestBody.put("temperature", 0.1);
|
||||||
|
// System提示词:让大模型结构化解析OCR文本
|
||||||
|
JSONObject sysMsg = new JSONObject();
|
||||||
|
sysMsg.put("role", "system");
|
||||||
|
sysMsg.put("content", StringUtil.RESUME_SYSTEM_PROMPT);
|
||||||
|
|
||||||
|
JSONObject userMsg = new JSONObject();
|
||||||
|
userMsg.put("role", "user");
|
||||||
|
userMsg.put("content", "以下是OCR识别内容:\n" + contextText);
|
||||||
|
|
||||||
|
requestBody.put("messages", new Object[]{sysMsg, userMsg});
|
||||||
|
requestBody.put("temperature", 0.3);
|
||||||
|
|
||||||
|
// 3. 请求头
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
HttpEntity<JSONObject> entity = new HttpEntity<>(requestBody, headers);
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONObject errorResp = new JSONObject();
|
||||||
|
ResponseEntity<JSONObject> response = longTimeoutRestTemplate.postForEntity(llmChatUrl, entity, JSONObject.class);
|
||||||
|
JSONObject result = response.getBody();
|
||||||
|
if (Objects.isNull(result)) {
|
||||||
|
log.error("大模型返回空响应");
|
||||||
|
errorResp.put("msg", "大模型调用失败:" + "大模型返回空响应");
|
||||||
|
return errorResp;
|
||||||
|
}
|
||||||
|
// 从OpenAI返回结构提取content
|
||||||
|
JSONArray choices = result.getJSONArray("choices");
|
||||||
|
if (Objects.isNull(choices) || choices.isEmpty()) {
|
||||||
|
log.error("大模型choices为空");
|
||||||
|
errorResp.put("msg", "大模型调用失败:" + "大模型choices为空");
|
||||||
|
return errorResp;
|
||||||
|
}
|
||||||
|
JSONObject choice = choices.getJSONObject(0);
|
||||||
|
JSONObject message = choice.getJSONObject("message");
|
||||||
|
String jsonStr = message.getString("content");
|
||||||
|
|
||||||
|
// 清洗多余文字,只保留{}内JSON
|
||||||
|
int start = jsonStr.indexOf("{");
|
||||||
|
int end = jsonStr.lastIndexOf("}");
|
||||||
|
if (start == -1 || end <= start) {
|
||||||
|
log.error("模型输出无合法JSON:" + jsonStr);
|
||||||
|
errorResp.put("msg", "大模型调用失败:" + "模型输出无合法JSON:" + jsonStr);
|
||||||
|
return errorResp;
|
||||||
|
}
|
||||||
|
String pureJson = jsonStr.substring(start, end + 1);
|
||||||
|
|
||||||
|
// 直接转简历实体
|
||||||
|
UserInfoDetail detail = JSON.parseObject(pureJson, UserInfoDetail.class);
|
||||||
|
|
||||||
|
// 原有userId业务逻辑保留
|
||||||
|
if (Objects.nonNull(userId) && userId > 0 && Objects.nonNull(detail)) {
|
||||||
|
dictEdit(detail);//处理字典
|
||||||
|
detail.setUserId(userId);
|
||||||
|
editUserOtherInfo(detail,true);
|
||||||
|
}
|
||||||
|
return JSON.parseObject(pureJson);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 捕获异常返回错误对象
|
||||||
|
JSONObject errorResp = new JSONObject();
|
||||||
|
errorResp.put("code", 500);
|
||||||
|
errorResp.put("msg", "大模型调用失败:" + e.getMessage());
|
||||||
|
return errorResp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理字典
|
||||||
|
* @param detail
|
||||||
|
*/
|
||||||
|
public void dictEdit(UserInfoDetail detail){
|
||||||
|
List<BussinessDictData> dictData = iBussinessDictTypeService.selectDictDataByType("education");
|
||||||
|
List<BussinessDictData> paData = iBussinessDictTypeService.selectDictDataByType("political_affiliation");
|
||||||
|
List<BussinessDictData> areaData = iBussinessDictTypeService.selectDictDataByType("area");
|
||||||
|
detail.setEducation(iBussinessDictDataService.findCode(dictData, detail.getEducation()));
|
||||||
|
detail.setPoliticalAffiliation(iBussinessDictDataService.findCode(paData, detail.getPoliticalAffiliation()));
|
||||||
|
String area=Objects.requireNonNull(iBussinessDictDataService.findCode(areaData, detail.getArea()));
|
||||||
|
detail.setArea(area);
|
||||||
|
List<String> jobNames=detail.getJobTitle();
|
||||||
|
if(!CollectionUtils.isEmpty(jobNames)){
|
||||||
|
String jobIds=iJobTitleService.queryNameToJobIds(jobNames);
|
||||||
|
detail.setJobTitleId(jobIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void editUserOtherInfo(UserInfoDetail detail, boolean flag) throws Exception {
|
public void editUserOtherInfo(UserInfoDetail detail, boolean flag) throws Exception {
|
||||||
try {
|
try {
|
||||||
@@ -1008,6 +1126,7 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
|
|||||||
if(StringUtils.isNotEmpty(appUser.getAddress())&&Pattern.compile(regex).matcher(appUser.getAddress()).find()){
|
if(StringUtils.isNotEmpty(appUser.getAddress())&&Pattern.compile(regex).matcher(appUser.getAddress()).find()){
|
||||||
appUser.setAddress(null);
|
appUser.setAddress(null);
|
||||||
}
|
}
|
||||||
|
appUser.setRegionCode(StringUtil.getAreaToRegionCode(appUser.getArea()));
|
||||||
appUserMapper.updateById(appUser);
|
appUserMapper.updateById(appUser);
|
||||||
|
|
||||||
if(CollectionUtil.isNotEmpty(detail.getWorkExp())){
|
if(CollectionUtil.isNotEmpty(detail.getWorkExp())){
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.*;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSONArray;
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.ruoyi.cms.constant.CommonConstant;
|
import com.ruoyi.cms.constant.CommonConstant;
|
||||||
import com.ruoyi.cms.util.JobTitleUtils;
|
import com.ruoyi.cms.util.JobTitleUtils;
|
||||||
@@ -245,4 +246,15 @@ public class JobTitleServiceImpl extends ServiceImpl<JobTitleMapper,JobTitle> im
|
|||||||
}
|
}
|
||||||
return labelSet;
|
return labelSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String queryNameToJobIds(List<String> jobNames){
|
||||||
|
LambdaQueryWrapper<JobTitle> wrapper = Wrappers.lambdaQuery();
|
||||||
|
wrapper.in(JobTitle::getJobName, jobNames);
|
||||||
|
List<JobTitle> jobList = jobTitleMapper.selectList(wrapper);
|
||||||
|
String jobIds = jobList.stream()
|
||||||
|
.map(item -> item.getJobId().toString())
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
return jobIds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ public class StringUtil {
|
|||||||
" \"position\": \"\",\n" +
|
" \"position\": \"\",\n" +
|
||||||
" \"startTime\": \"\",\n" +
|
" \"startTime\": \"\",\n" +
|
||||||
" \"endTime\": \"\",\n" +
|
" \"endTime\": \"\",\n" +
|
||||||
" \"workContent\": \"\"\n" +
|
" \"description\": \"\"\n" +
|
||||||
" }\n" +
|
" }\n" +
|
||||||
" ],\n" +
|
" ],\n" +
|
||||||
" \"projectExp\": [\n" +
|
" \"projectExp\": [\n" +
|
||||||
@@ -224,8 +224,8 @@ public class StringUtil {
|
|||||||
" ],\n" +
|
" ],\n" +
|
||||||
" \"skillList\": [\n" +
|
" \"skillList\": [\n" +
|
||||||
" {\n" +
|
" {\n" +
|
||||||
" \"skillName\": \"\",\n" +
|
" \"name\": \"\",\n" +
|
||||||
" \"skillLevel\": \"\"\n" +
|
" \"levels\": \"\"\n" +
|
||||||
" }\n" +
|
" }\n" +
|
||||||
" ],\n" +
|
" ],\n" +
|
||||||
" \"professionalTechnicalPostList\": [\n" +
|
" \"professionalTechnicalPostList\": [\n" +
|
||||||
|
|||||||
Reference in New Issue
Block a user