@@ -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用户
@@ -680,7 +699,7 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> 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<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 ( ) ) ) {