添加ocr识别简历
This commit is contained in:
@@ -2,6 +2,7 @@ package com.ruoyi.cms.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.AppUserShow;
|
||||
import com.ruoyi.common.core.domain.entity.MyChart;
|
||||
import com.ruoyi.common.core.domain.entity.AppUser;
|
||||
@@ -85,4 +86,6 @@ public interface IAppUserService
|
||||
public AppUser getYtjValidPhone(String phone);
|
||||
|
||||
public AppUser getYtjValidIdcard(String phone);
|
||||
|
||||
AjaxResult recognition(byte[] bytes, String fileName, Long userId);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
package com.ruoyi.cms.service.impl;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.ruoyi.cms.domain.UserInfoDetail;
|
||||
import com.ruoyi.cms.util.AppUserFieldCustomCopy;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.MyChart;
|
||||
import com.ruoyi.common.core.domain.entity.File;
|
||||
import com.ruoyi.cms.domain.vo.AppSkillVo;
|
||||
@@ -18,11 +24,22 @@ import com.ruoyi.common.core.domain.model.RegisterBody;
|
||||
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.springframework.beans.factory.annotation.Autowired;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.cms.service.IAppUserService;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* APP用户Service业务层处理
|
||||
@@ -48,6 +65,16 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
|
||||
@Autowired
|
||||
private FileMapper fileMapper;
|
||||
|
||||
@Value("${ocr.ocr_mutipart}")
|
||||
private String ocrMutipartUrl;
|
||||
// 大模型配置
|
||||
@Value("${ocr.ocr_llm_url}")
|
||||
private String llmApiUrl;
|
||||
@Value("${ocr.ocr_llm_apiKey}")
|
||||
private String llmApiKey;
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
/**
|
||||
* 查询APP用户
|
||||
*
|
||||
@@ -744,4 +771,189 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
|
||||
.eq(AppUser::getDelFlag,"0")
|
||||
.orderByDesc(AppUser::getUpdateTime).last("LIMIT 1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AjaxResult recognition(byte[] bytes, String fileName, Long userId){
|
||||
// --- 第一步:调用 OCR 服务 ---
|
||||
String ocrText = callOcrService(bytes,fileName);
|
||||
|
||||
if (ocrText == null || ocrText.isEmpty()) {
|
||||
return AjaxResult.error("OCR识别内容为空或失败");
|
||||
}
|
||||
|
||||
// --- 第二步:调用大模型进行简历分析 ---
|
||||
JSONObject llmResult = callLlmService(ocrText,userId);
|
||||
if(llmResult.containsKey("error")){
|
||||
return AjaxResult.error(llmResult.getString("error"));
|
||||
}
|
||||
return AjaxResult.success(llmResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 OCR 接口
|
||||
*/
|
||||
private String callOcrService(byte[] bytes,String fileName) {
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||
|
||||
// 构建文件资源,重写 getFilename 确保服务端能识别文件名
|
||||
ByteArrayResource fileResource = new ByteArrayResource(bytes) {
|
||||
@Override
|
||||
public String getFilename() {
|
||||
return fileName;
|
||||
}
|
||||
};
|
||||
|
||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||
body.add("file", fileResource);
|
||||
|
||||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
|
||||
|
||||
// 发送请求
|
||||
ResponseEntity<JSONObject> response = restTemplate.postForEntity(ocrMutipartUrl, requestEntity, JSONObject.class);
|
||||
JSONObject resultBody = response.getBody();
|
||||
|
||||
if (resultBody != null && resultBody.getInteger("code") == 200) {
|
||||
return resultBody.getString("data"); // 获取识别出的文本
|
||||
} else {
|
||||
// 记录日志:OCR调用失败 msg: resultBody.getString("msg")
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用大模型接口
|
||||
*/
|
||||
private JSONObject callLlmService(String contextText,Long userId) {
|
||||
// 1. 创建一个具有长超时时间的 Factory
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
// 设置连接超时(握手时间),10秒通常够了
|
||||
factory.setConnectTimeout(10000);
|
||||
// 【关键】设置读取超时。大模型分析简历可能需要较长时间,建议设为 60秒 或 120秒
|
||||
factory.setReadTimeout(120000);
|
||||
|
||||
// 2. 使用这个 Factory 创建一个新的 RestTemplate
|
||||
RestTemplate longTimeoutRestTemplate = new RestTemplate(factory);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("Authorization", "Bearer " + llmApiKey);
|
||||
|
||||
JSONObject requestBody = new JSONObject();
|
||||
requestBody.put("doc_list", new Object[]{});
|
||||
requestBody.put("image_url", "");
|
||||
requestBody.put("query", contextText);
|
||||
requestBody.put("session_id", "");
|
||||
// 保持 false,让后端等待所有结果生成完再一次性拿回
|
||||
requestBody.put("stream", false);
|
||||
|
||||
HttpEntity<JSONObject> entity = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
try {
|
||||
// 3. 使用新的 longTimeoutRestTemplate 发送请求
|
||||
ResponseEntity<JSONObject> response = longTimeoutRestTemplate.postForEntity(llmApiUrl, entity, JSONObject.class);
|
||||
JSONObject o = response.getBody();
|
||||
if(Objects.nonNull(userId)&&userId > 0){
|
||||
String msg = buildUserDetailInfo(o,userId);
|
||||
o = new JSONObject();
|
||||
if(msg.equals("error")){
|
||||
o.put("error",msg);
|
||||
}
|
||||
}
|
||||
return o;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
JSONObject error = new JSONObject();
|
||||
// 打印具体错误以便调试
|
||||
error.put("error", "LLM调用失败: " + e.getMessage());
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
private String buildUserDetailInfo(JSONObject object,Long userId){
|
||||
String msg = "";
|
||||
try {
|
||||
if("200".equals(object.getString("code"))){
|
||||
object = object.getJSONObject("data");
|
||||
object = object.getJSONObject("data");
|
||||
object = object.getJSONObject("output");
|
||||
UserInfoDetail detail = JSONObject.parseObject(object.toJSONString(),UserInfoDetail.class);
|
||||
if(StringUtils.isNotEmpty(detail.getArea())){
|
||||
String area = detail.getArea();
|
||||
area = area.replaceAll("\\[","");
|
||||
area = area.replaceAll("\\]","");
|
||||
area = area.trim();
|
||||
detail.setArea(area);
|
||||
}
|
||||
detail.setUserId(userId);
|
||||
editUserOtherInfo(detail,true);
|
||||
}
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
msg = e.getMessage();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void editUserOtherInfo(UserInfoDetail detail, boolean flag) throws Exception {
|
||||
try {
|
||||
Long userId = detail.getUserId();
|
||||
AppUser appUser = appUserMapper.selectById(userId);
|
||||
//先将appUser没有的或不同的数据拷给appUser
|
||||
if(flag){
|
||||
//条件拷贝,当detail数据来源于第三方或简历解析时,已现有的appUser数据为主
|
||||
appUser = new AppUserFieldCustomCopy().conditionalCopy(appUser,detail);
|
||||
}else{
|
||||
BeanUtils.copyPropertiesIgnoreNull(appUser, detail);
|
||||
}
|
||||
//再将detail没有的数据拷给它
|
||||
BeanUtils.copyPropertiesIgnoreNull(detail, appUser);
|
||||
|
||||
String regex = "[@#$%&*-_ ]"; // 黑名单字符,空格也写在这里面
|
||||
|
||||
if(StringUtils.isNotEmpty(appUser.getName())&& Pattern.compile(regex).matcher(appUser.getName()).find()){
|
||||
appUser.setName(null);
|
||||
}
|
||||
if(StringUtils.isNotEmpty(appUser.getPhone())&&Pattern.compile(regex).matcher(appUser.getPhone()).find()){
|
||||
appUser.setPhone(null);
|
||||
}
|
||||
if(StringUtils.isNotEmpty(appUser.getIdCard())&&Pattern.compile(regex).matcher(appUser.getIdCard()).find()){
|
||||
appUser.setIdCard(null);
|
||||
}
|
||||
if(StringUtils.isNotEmpty(appUser.getAddress())&&Pattern.compile(regex).matcher(appUser.getAddress()).find()){
|
||||
appUser.setAddress(null);
|
||||
}
|
||||
appUserMapper.updateById(appUser);
|
||||
|
||||
if(CollectionUtil.isNotEmpty(detail.getWorkExp())){
|
||||
List<UserWorkExperiences> list = userWorkExperiencesMapper.selectList(Wrappers.lambdaQuery(UserWorkExperiences.class).eq(UserWorkExperiences::getUserId,userId));
|
||||
if(CollectionUtil.isEmpty(list)){
|
||||
for(UserWorkExperiences workExperience : detail.getWorkExp()){
|
||||
workExperience.setUserId(userId);
|
||||
userWorkExperiencesMapper.insert(workExperience);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(CollectionUtil.isNotEmpty(detail.getSkillList())){
|
||||
List<AppSkill> list = appSkillMapper.selectList(Wrappers.lambdaQuery(AppSkill.class).eq(AppSkill::getUserId,userId));
|
||||
if(CollectionUtil.isNotEmpty(list)){
|
||||
appSkillMapper.deleteBatchIds(list.stream().map(AppSkill::getId).collect(Collectors.toList()));
|
||||
}
|
||||
for(AppSkill skill : detail.getSkillList()){
|
||||
skill.setUserId(userId);
|
||||
appSkillMapper.insert(skill);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new Exception(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user