添加简历识别模块—ocr缺少重新打包

This commit is contained in:
sh
2026-07-23 16:26:11 +08:00
parent b0ea553370
commit 332bff8bf0
5 changed files with 143 additions and 10 deletions

View File

@@ -221,11 +221,11 @@ file:
ocr:
ocr_url: http://127.0.0.1:9001/ocr
ocr_mutipart: https://qd.zhaopinzao8dian.com/ocr-api/ocr
# ocr_mutipart: http://10.98.80.141:9000/ocr
# ocr_mutipart: https://qd.zhaopinzao8dian.com/ocr-api/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_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:
socket: 39.102.146.78:11028

View File

@@ -65,4 +65,6 @@ public interface IJobTitleService
List<JobTitle> levelOne();
Set<String> jobTitleNameSets();
String queryNameToJobIds(List<String> jobNames);
}

View File

@@ -1,17 +1,23 @@
package com.ruoyi.cms.service.impl;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.cms.domain.BussinessDictData;
import com.ruoyi.cms.domain.JobApply;
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.DictUtils;
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.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Value;
@@ -71,6 +78,12 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
private FileMapper fileMapper;
@Autowired
private JobApplyMapper jobApplyMapper;
@Autowired
private IBussinessDictTypeService iBussinessDictTypeService;
@Autowired
private IBussinessDictDataService iBussinessDictDataService;
@Autowired
private IJobTitleService iJobTitleService;
@Value("${ocr.ocr_mutipart}")
private String ocrMutipartUrl;
@@ -80,10 +93,16 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
@Value("${ocr.ocr_llm_apiKey}")
private String llmApiKey;
@Value("${ocr.ocr_llm_chatUrl}")
private String llmChatUrl;
private final RestTemplate restTemplate = new RestTemplate();
@Autowired
private OauthClient oauthClient;
@Autowired
private ExecutorService sseLlmExecutor;
/**
* 查询APP用户
@@ -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")){
return AjaxResult.error(llmResult.getString("error"));
}
@@ -979,6 +999,104 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
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)
public void editUserOtherInfo(UserInfoDetail detail, boolean flag) throws Exception {
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()){
appUser.setAddress(null);
}
appUser.setRegionCode(StringUtil.getAreaToRegionCode(appUser.getArea()));
appUserMapper.updateById(appUser);
if(CollectionUtil.isNotEmpty(detail.getWorkExp())){

View File

@@ -4,6 +4,7 @@ import java.util.*;
import java.util.stream.Collectors;
import com.alibaba.fastjson2.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.cms.constant.CommonConstant;
import com.ruoyi.cms.util.JobTitleUtils;
@@ -245,4 +246,15 @@ public class JobTitleServiceImpl extends ServiceImpl<JobTitleMapper,JobTitle> im
}
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;
}
}

View File

@@ -204,7 +204,7 @@ public class StringUtil {
" \"position\": \"\",\n" +
" \"startTime\": \"\",\n" +
" \"endTime\": \"\",\n" +
" \"workContent\": \"\"\n" +
" \"description\": \"\"\n" +
" }\n" +
" ],\n" +
" \"projectExp\": [\n" +
@@ -224,8 +224,8 @@ public class StringUtil {
" ],\n" +
" \"skillList\": [\n" +
" {\n" +
" \"skillName\": \"\",\n" +
" \"skillLevel\": \"\"\n" +
" \"name\": \"\",\n" +
" \"levels\": \"\"\n" +
" }\n" +
" ],\n" +
" \"professionalTechnicalPostList\": [\n" +