From 332bff8bf0ba59192d20d7dd6f08d1135bf50d4f Mon Sep 17 00:00:00 2001 From: sh Date: Thu, 23 Jul 2026 16:26:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=AE=80=E5=8E=86=E8=AF=86?= =?UTF-8?q?=E5=88=AB=E6=A8=A1=E5=9D=97=E2=80=94ocr=E7=BC=BA=E5=B0=91?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-dev.yml | 6 +- .../ruoyi/cms/service/IJobTitleService.java | 2 + .../cms/service/impl/AppUserServiceImpl.java | 127 +++++++++++++++++- .../cms/service/impl/JobTitleServiceImpl.java | 12 ++ .../java/com/ruoyi/cms/util/StringUtil.java | 6 +- 5 files changed, 143 insertions(+), 10 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 7cc3309..01af4f4 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -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 diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IJobTitleService.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IJobTitleService.java index 88d2fd4..ba46b48 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IJobTitleService.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IJobTitleService.java @@ -65,4 +65,6 @@ public interface IJobTitleService List levelOne(); Set jobTitleNameSets(); + + String queryNameToJobIds(List jobNames); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AppUserServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AppUserServiceImpl.java index e9d3eec..78489a5 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AppUserServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AppUserServiceImpl.java @@ -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 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 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用户 @@ -680,7 +699,7 @@ public class AppUserServiceImpl extends ServiceImpl imple public AppUser getUserInfo() { //查询用户信息 LoginUser loginUser=SecurityUtils.getLoginUser(); - System.out.println("loginUser========================"+JSON.toJSONString(loginUser)); + System.out.println("loginUser========================"+ JSON.toJSONString(loginUser)); SysUser sysUser=loginUser.getUser(); System.out.println("sysUser========================"+JSON.toJSONString(sysUser)); AppUser appUser=selectAppuserByIdcard(sysUser.getIdCard()); @@ -861,7 +880,8 @@ public class AppUserServiceImpl extends ServiceImpl 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 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 entity = new HttpEntity<>(requestBody, headers); + + try { + JSONObject errorResp = new JSONObject(); + ResponseEntity 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 dictData = iBussinessDictTypeService.selectDictDataByType("education"); + List paData = iBussinessDictTypeService.selectDictDataByType("political_affiliation"); + List 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 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 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())){ diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleServiceImpl.java index fd5e37d..9c9fc37 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleServiceImpl.java @@ -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 im } return labelSet; } + + @Override + public String queryNameToJobIds(List jobNames){ + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.in(JobTitle::getJobName, jobNames); + List jobList = jobTitleMapper.selectList(wrapper); + String jobIds = jobList.stream() + .map(item -> item.getJobId().toString()) + .collect(Collectors.joining(",")); + return jobIds; + } } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/StringUtil.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/StringUtil.java index d09c5c4..9599ddb 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/StringUtil.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/StringUtil.java @@ -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" +