package com.engine.recruit.service.impl; import cn.hutool.core.convert.Convert; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.engine.core.impl.Service; import com.engine.recruit.conn.*; import com.engine.recruit.constant.RecruitConstant; import com.engine.recruit.enums.ApplicationStatusEnum; import com.engine.recruit.exception.CustomizeRunTimeException; import com.engine.recruit.service.ResumeRecognitionService; import com.weaver.formmodel.data.model.Formfield; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import weaver.common.DateUtil; import weaver.conn.RecordSet; import weaver.file.ImageFileManager; import weaver.formmode.recruit.modeexpand.util.RecruitModeUtil; import weaver.general.BaseBean; import weaver.general.Util; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; /** * @author:dxfeng * @createTime: 2023/12/12 * @version: 1.0 */ public class ResumeRecognitionServiceImpl extends Service implements ResumeRecognitionService { @Override public Map resumeUpload(Map param) { Map returnMap = new HashMap<>(16); String resumeId = Util.null2String(param.get("resumeId")); int imageField = ApplicantCommonInfo.getImageFieldByDocId(resumeId); if (-1 == imageField) { throw new CustomizeRunTimeException("原始简历文件获取失败,请重新上传原始简历"); } qllResumeUpload(imageField, returnMap, true); returnMap.put("isOcr", true); return returnMap; } @Override public Map importResume(Map param) { Map returnMap = new HashMap<>(); String resumeId = Util.null2String(param.get("resumeId")); Map uploadDataMap = new HashMap<>(); qllResumeUpload(Convert.toInt(resumeId), uploadDataMap, false); Map resumeData = (Map) uploadDataMap.get("data"); // 添加原始附件字段信息 int secCategory = Convert.toInt(RecruitConstant.APPLICANTS_RESUMES_CATEGORY); try { int docId = RecruitModeUtil.createDocId(secCategory, Convert.toInt(resumeId), user); resumeData.put("ysjl", docId); } catch (Exception e) { throw new RuntimeException(e); } List> studyList = (List>) resumeData.remove("jyjl"); List> workList = (List>) resumeData.remove("gzjl"); List> projectList = (List>) resumeData.remove("xmjy"); // 状态 resumeData.put("zt", ApplicationStatusEnum.DISTRIBUTION.getValue()); List fieldList = RecruitModeUtil.getFieldList("uf_jcl_yppc"); Set keySet = fieldList.stream().map(Formfield::getFieldname).collect(Collectors.toSet()); // 移除不在 keySet 中的键值对 resumeData.entrySet().removeIf(entry -> !keySet.contains(entry.getKey())); // 判断简历信息 CheckRepeatResume instance = CheckRepeatResume.getInstance(); Map map = instance.insertResumeAndReturn(user.getUID(), resumeData); String mainId = Util.null2String(map.get("mainId")); String sourceId = Util.null2String(map.get("sourceId")); returnMap.put("mainId", mainId); returnMap.put("sourceId", sourceId); returnMap.put("id", StringUtils.isNotBlank(sourceId) ? sourceId : mainId); instance.insertResumeDetailTable(studyList, "uf_jcl_yppc_dt1", mainId, sourceId); instance.insertResumeDetailTable(workList, "uf_jcl_yppc_dt2", mainId, sourceId); instance.insertResumeDetailTable(projectList, "uf_jcl_yppc_dt3", mainId, sourceId); return returnMap; } @Override public Map fetchResume(Map param) { String id = Util.null2String(param.get("id")); String sql = "select * from uf_jcl_yppc where id = ? "; RecordSet rs = new RecordSet(); rs.executeQuery(sql, id); Map dataMap = RecruitRecordSet.getSingleRecordMap(rs); List fieldList = RecruitModeUtil.getFieldList("uf_jcl_yppc"); Map fieldMap = fieldList.stream().collect(Collectors.toMap(Formfield::getFieldname, item -> item, (k1, k2) -> k1)); for (String key : dataMap.keySet()) { Object value = dataMap.get(key); if (StringUtils.isBlank(Convert.toStr(value))) { continue; } // 计算新的value值 Formfield formfield = fieldMap.get(key); value = RecruitModeUtil.getFieldShowName(formfield, Convert.toStr(value)).replaceAll("<[^>]*>", ""); // 更新value值 dataMap.put(key, value); } return dataMap; } /** * @param resumeId 简历ID * @param returnMap 响应集合 */ private void qllResumeUpload(int resumeId, Map returnMap, boolean isCard) { String response = doQllPost(resumeId); if (StringUtils.isBlank(response)) { throw new CustomizeRunTimeException("千里聆接口调用失败,响应结果为空"); } BaseBean baseBean = new BaseBean(); JSONObject all = JSONObject.parseObject(response); if (!all.getBoolean("isSuccess")) { baseBean.writeLog("千里聆响应数据:" + all); throw new CustomizeRunTimeException(all.getString("errorMsg")); } JSONObject resultall = all.getJSONObject("data"); String status = resultall.getString("state"); if ("fail".equals(status)) { throw new CustomizeRunTimeException("调用千里聆接口失败,失败原因:" + resultall.getString("info")); } baseBean.writeLog("千里聆OCR解析数据:" + resultall); JSONObject result = resultall.getJSONObject("result"); Map dataMap = parseQllJsonToMapV2(result, isCard); // 解析图片信息 String face_base64 = resultall.getString("face_base64"); if (StringUtils.isNotBlank(face_base64)) { try { byte[] decodedBytes = Base64.getDecoder().decode(face_base64); InputStream inputStream = new ByteArrayInputStream(decodedBytes); int imageFileId = RecruitModeUtil.generateImageFileId(inputStream, dataMap.get("xm") + "-" + UUID.randomUUID() + ".png"); int docId = RecruitModeUtil.createDocId(Convert.toInt(RecruitConstant.APPLICANTS_RESUMES_CATEGORY), imageFileId, user); dataMap.put("jlzp", docId); } catch (Exception e) { baseBean.writeLog("千里聆OCR简历照片解析失败:" + e); } } returnMap.put("data", dataMap); } /** * 千里聆解析字段内容处理(V2) * * @param obj * @return */ private Map parseQllJsonToMapV2(JSONObject obj, boolean isCard) { Map dataMap = new HashMap<>(16); JSONObject personalInformation = obj.getJSONObject("个人信息"); // 投递时间 dataMap.put("tdsj", DateUtil.getDateTime()); // 姓名 String xm = personalInformation.getString("姓名"); dataMap.put("xm", xm); // 电子邮箱 String dzyx = personalInformation.getString("电子邮箱"); dataMap.put("dzyx", dzyx); // 手机号码 String sjhm = personalInformation.getString("手机号"); dataMap.put("sjhm", sjhm); // 年龄 String nl = personalInformation.getString("年龄"); if (StringUtils.isNotBlank(nl)) { dataMap.put("nl", nl); } // 出生日期 String csrq = personalInformation.getString("出生日期"); dataMap.put("csrq", parseDateObject(csrq)); // 性别 默认为男 String xb = personalInformation.getString("性别"); if ("女".equals(xb)) { dataMap.put("xb", 1); } else if ("男".equals(xb)) { dataMap.put("xb", 0); } else { dataMap.put("xb", null); } // 体重(KG) String tz = personalInformation.getString("体重"); dataMap.put("tzkg", Convert.toDouble(tz)); // 身高(CM) String sg = personalInformation.getString("身高"); dataMap.put("sgcm", Convert.toDouble(sg)); // 籍贯(字段类型不支持) String jg = personalInformation.getString("籍贯"); //dataMap.put("jg", parseArray(obj.getJSONArray("籍贯"))); // 婚姻状况 List> hyzk = getBrowserArray(personalInformation.getString("婚姻状况"), ModeBrowserCommonInfo.TYPE_MARITAL_STATUS); if (CollectionUtils.isNotEmpty(hyzk)) { dataMap.put("hyzk", hyzk); if (!isCard) { dataMap.put("hyzk", hyzk.stream().map(item -> item.get("id")).collect(Collectors.joining(","))); } } // 政治面貌 List> zzmm = getBrowserArray(personalInformation.getString("政治面貌"), ModeBrowserCommonInfo.TYPE_POLITICAL_LANDSCAPE); if (CollectionUtils.isNotEmpty(zzmm)) { dataMap.put("zzmm", zzmm); if (!isCard) { dataMap.put("zzmm", zzmm.stream().map(item -> item.get("id")).collect(Collectors.joining(","))); } } // 在职状态 List> zzzt = getBrowserArray(personalInformation.getString("在职状态"), ModeBrowserCommonInfo.TYPE_ON_THE_JOB_STATUS); if (CollectionUtils.isNotEmpty(zzzt)) { dataMap.put("zzzt", zzzt); if (!isCard) { dataMap.put("zzzt", zzzt.stream().map(item -> item.get("id")).collect(Collectors.joining(","))); } } // 工作经验 String gzjyStr = personalInformation.getString("工作经验"); if (StringUtils.isNotBlank(gzjyStr)) { if (gzjyStr.contains("到")) { String[] split = gzjyStr.split("到"); if (split.length == 2) { gzjyStr = split[1]; } } } List> gzjy = getBrowserArray(gzjyStr, ModeBrowserCommonInfo.TYPE_WORK_EXPERIENCE); if (CollectionUtils.isNotEmpty(gzjy)) { dataMap.put("gzjy", gzjy); if (!isCard) { dataMap.put("gzjy", gzjy.stream().map(item -> item.get("id")).collect(Collectors.joining(","))); } } // 最高学位 List> zgxw = getBrowserArray(personalInformation.getString("最高学位"), ModeBrowserCommonInfo.TYPE_DEGREE); if (CollectionUtils.isNotEmpty(zgxw)) { dataMap.put("zgxw", zgxw); if (!isCard) { dataMap.put("zgxw", zgxw.stream().map(item -> item.get("id")).collect(Collectors.joining(","))); } } // 最高学历 String highestEduLevel = personalInformation.getString("最高学历"); List> zgxl = getBrowserArray(highestEduLevel, this::getEducationLevelArray); if (CollectionUtils.isNotEmpty(zgxl)) { dataMap.put("zgxl", zgxl); if (!isCard) { dataMap.put("zgxl", zgxl.stream().map(item -> item.get("id")).collect(Collectors.joining(","))); } } // 毕业院校 String byyx = personalInformation.getString("毕业院校"); dataMap.put("byyx", byyx); // 现税前月薪(K) // 自我评价 String zwpj = personalInformation.getString("个人评价"); dataMap.put("zwpj", zwpj); // 民族 dataMap.put("mz", personalInformation.getString("民族")); JSONObject jobSearchInformation = obj.getJSONObject("求职信息"); // 期望税前月薪(K) String qwxz = jobSearchInformation.getString("期望薪资"); dataMap.put("qwxz", qwxz); // 明细表数据 // 教育经历 JSONArray jyjl = obj.getJSONArray("学业信息"); List> studyList = new ArrayList<>(); if (null != jyjl && jyjl.size() > 0) { for (int i = 0; i < jyjl.size(); i++) { JSONObject o = (JSONObject) jyjl.get(i); String studyDate = o.getString("就读时期"); RecruitDataMap studyMap = new RecruitDataMap<>(); studyMap.putAll(getDateRange(studyDate, true)); String schoolName = o.getString("学校名称"); studyMap.put("xxmc", schoolName); String educationLevel = o.getString("学历"); List> xl = getBrowserArray(educationLevel, this::getEducationLevelArray); studyMap.put("xl", xl); if (!isCard && CollectionUtils.isNotEmpty(xl)) { studyMap.put("xl", xl.stream().map(item -> item.get("id")).collect(Collectors.joining(","))); } String professionalName = o.getString("专业名称"); studyMap.put("zy", professionalName); // 主表 专业字段 if (byyx.equals(schoolName) && highestEduLevel.equals(educationLevel)) { dataMap.put("zy", professionalName); } studyList.add(studyMap); } } dataMap.put("jyjl", studyList); // 工作经历 JSONArray gzjl = obj.getJSONArray("工作经历"); List> workList = new ArrayList<>(); if (null != gzjl && gzjl.size() > 0) { for (int i = 0; i < gzjl.size(); i++) { JSONObject o = (JSONObject) gzjl.get(i); String workDate = o.getString("工作时间"); RecruitDataMap workMap = new RecruitDataMap<>(); workMap.putAll(getDateRange(workDate, false)); workMap.put("gsmc", o.getString("工作单位")); workMap.put("gw", o.getString("岗位名称")); workMap.put("gzzz", o.getString("工作内容")); workList.add(workMap); } } dataMap.put("gzjl", workList); // 项目经验 JSONArray xmjy = obj.getJSONArray("项目经验"); List> projectList = new ArrayList<>(); if (null != xmjy && xmjy.size() > 0) { for (int i = 0; i < xmjy.size(); i++) { JSONObject o = (JSONObject) xmjy.get(i); String startDate = getFormatDate(o.getString("开始时间")); String endDate = getFormatDate(o.getString("结束时间")); RecruitDataMap projectMap = new RecruitDataMap<>(); projectMap.put("kssj", startDate); projectMap.put("jssj", endDate); projectMap.put("xmmc", o.getString("项目名称")); projectMap.put("drjs", o.getString("担任角色")); projectMap.put("xmms", o.getString("项目描述")); projectList.add(projectMap); } } dataMap.put("xmjy", projectList); // ###简历表新增字段 2024-11-19 // 当前所在地(字段类型不支持) String xjzd = personalInformation.getString("现居住地"); dataMap.put("xjzd", xjzd); String qqh = personalInformation.getString("QQ号"); dataMap.put("qqh", qqh); String wxh = personalInformation.getString("微信号"); dataMap.put("wxh", wxh); String xqah = personalInformation.getString("兴趣爱好"); dataMap.put("xqah", xqah); String xyjl = personalInformation.getString("校园经历"); dataMap.put("xyjl", xyjl); JSONArray zyjnArray = obj.getJSONArray("专业技能"); List skillList = new ArrayList<>(); zyjnArray.forEach(item -> { JSONObject o = (JSONObject) item; String skillName = o.getString("名称"); String skillLevel = o.getString("熟练程度"); if (StringUtils.isNotBlank(skillName) && StringUtils.isNotBlank(skillLevel)) { skillList.add(skillName + ":" + skillLevel); } }); if (CollectionUtils.isNotEmpty(skillList)) { dataMap.put("zyjn", StringUtils.join(skillList,"\n")); } JSONArray zsjxArray = obj.getJSONArray("证书奖项"); List certificateList = new ArrayList<>(); zsjxArray.forEach(item -> { JSONObject o = (JSONObject) item; String certificateName = o.getString("名称"); if (StringUtils.isNotBlank(certificateName)) { certificateList.add(certificateName); } }); if (CollectionUtils.isNotEmpty(certificateList)) { dataMap.put("ryzs", StringUtils.join(certificateList,"\n")); } // 新增明细表 // 语言能力 JSONArray ylnl = obj.getJSONArray("语言能力"); List> languageList = new ArrayList<>(); if (null != ylnl && ylnl.size() > 0) { for (int i = 0; i < ylnl.size(); i++) { JSONObject o = (JSONObject) ylnl.get(i); String languageType = o.getString("语言类型"); String languageLevel = o.getString("熟练程度"); if (StringUtils.isNotBlank(languageLevel)) { switch (languageLevel) { case "一般": languageLevel = "0"; break; case "良好": languageLevel = "1"; break; case "熟练": languageLevel = "2"; break; case "精通": languageLevel = "3"; break; default: languageLevel = ""; break; } } RecruitDataMap languageMap = new RecruitDataMap<>(); languageMap.put("yylx", languageType); languageMap.put("zwcd", languageLevel); languageList.add(languageMap); } } dataMap.put("ylnl", languageList); // 实习经历 JSONArray sxjl = obj.getJSONArray("实习经历"); List> internshipList = new ArrayList<>(); if (null != sxjl && sxjl.size() > 0) { for (int i = 0; i < sxjl.size(); i++) { JSONObject o = (JSONObject) sxjl.get(i); String workUnit = o.getString("工作单位"); String post = o.getString("职务"); String startTime = getFormatDate(o.getString("开始时间")); String endTime = getFormatDate(o.getString("结束时间")); String jobContent = o.getString("工作内容"); RecruitDataMap internshipMap = new RecruitDataMap<>(); internshipMap.put("sxgsmc", workUnit); internshipMap.put("sxgw", post); internshipMap.put("kssj", startTime); internshipMap.put("jssj", endTime); internshipMap.put("gzms", jobContent); internshipList.add(internshipMap); } } dataMap.put("sxjl", internshipList); return dataMap; } /** * @param resumeId * @return */ private String doQllPost(int resumeId) { String url = RecruitConstant.OCR_URL; String appId = RecruitConstant.APP_ID; String appSecret = RecruitConstant.APP_SECRET; if (StringUtils.isAnyBlank(url, appId, appSecret)) { throw new CustomizeRunTimeException("千里聆配置出错,请检查配置文件"); } long currentTime = System.currentTimeMillis(); ImageFileManager manager = new ImageFileManager(); manager.getImageFileInfoById(resumeId); HttpPost postRequest = new HttpPost(url); postRequest.addHeader("sign", getSign(appId, appSecret, currentTime)); postRequest.addHeader("appId", appId); postRequest.addHeader("timestamp", String.valueOf(currentTime)); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); String fileName = manager.getImageFileName(); builder.addTextBody("type", fileName.substring(fileName.lastIndexOf('.') + 1)); // 对接新版抽取服务 builder.addTextBody("version", "V2"); builder.addBinaryBody("resume", manager.getInputStream(), ContentType.APPLICATION_OCTET_STREAM, manager.getImageFileName()); HttpEntity entity = builder.build(); postRequest.setEntity(entity); try { CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(postRequest); HttpEntity responseEntity = response.getEntity(); return EntityUtils.toString(responseEntity); } catch (Exception e) { throw new CustomizeRunTimeException(e); } } /** * 解析头像 * * @param faceUrl * @param filename * @param dataMap */ private void getImageFile(String faceUrl, String filename, Map dataMap) { String appId = RecruitConstant.APP_ID; String appSecret = RecruitConstant.APP_SECRET; long currentTime = System.currentTimeMillis(); CloseableHttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(faceUrl); httpGet.addHeader("sign", getSign(appId, appSecret, currentTime)); httpGet.addHeader("appId", appId); httpGet.addHeader("timestamp", String.valueOf(currentTime)); // 执行请求并获取响应 try (CloseableHttpResponse response = httpClient.execute(httpGet)) { // 检查响应状态 if (response.getStatusLine().getStatusCode() == 200) { // 获取响应实体 InputStream inputStream = response.getEntity().getContent(); int imageFileId = RecruitModeUtil.generateImageFileId(inputStream, filename + ".png"); int docId = RecruitModeUtil.createDocId(Convert.toInt(RecruitConstant.APPLICANTS_RESUMES_CATEGORY), imageFileId, user); dataMap.put("jlzp", docId); } } catch (Exception e) { throw new CustomizeRunTimeException(e); } } /** * 千里聆签名 * * @param appId 开发者AppId * @param appSecret 开发者appSecret * @param timestamp 当前时间戳(毫秒数) * @return */ private static String getSign(String appId, String appSecret, long timestamp) { if (appId != null && appId.length() != 0) { if (appSecret != null && appSecret.length() != 0) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(appId.getBytes()); md5.update((timestamp + "").getBytes()); md5.update(appSecret.getBytes()); byte[] bytes = md5.digest(); return (new BigInteger(1, bytes)).toString(16); } catch (NoSuchAlgorithmException var8) { throw new CustomizeRunTimeException("不支持的加密算法", var8); } } else { throw new CustomizeRunTimeException("appSecret不能为空"); } } else { throw new CustomizeRunTimeException("appId不能为空"); } } /** * 转换日期对象 * * @param value * @return */ private String parseDateObject(String value) { if (StringUtils.isBlank(value)) { return null; } value = value.replace(".", "-"); DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); sdf.setLenient(false); try { sdf.parse(value); } catch (Exception e) { new BaseBean().writeLog("日期格式化失败", e); value = null; } return value; } /** * 获取开始时间和结束时间 * * @param date * @return */ private RecruitDataMap getDateRange(String date, boolean isStudy) { RecruitDataMap dataRangeMap = new RecruitDataMap(); if (StringUtils.isBlank(date)) { return dataRangeMap; } else { String[] split = date.split("-"); String end; if (split.length > 0) { end = getFormatDate(split[0]); if (end.length() == 4) { if (isStudy) { end = end + "-09-01"; } else { end = ""; } } dataRangeMap.put("kssj", end); } if (split.length > 1) { end = getFormatDate(split[1]); if (end.length() == 4) { if (isStudy) { end = end + "-07-01"; } else { end = ""; } } dataRangeMap.put("jssj", end); } return dataRangeMap; } } /** * 获取yyyy-MM-dd时间格式日期 * * @param dateStr * @return */ private String getFormatDate(String dateStr) { dateStr = dateStr.replace(".", "-").replace("\\/", "-"); if (dateStr.length() == 7) { return dateStr + "-01"; } else if (dateStr.length() == 10) { return dateStr; } else if (dateStr.length() == 4) { return dateStr; } return ""; } /** * 构建浏览按钮字段信息格式 * * @param text * @param fun * @return */ private List> getBrowserArray(String text, Function> fun) { List> list = new ArrayList<>(); if (StringUtils.isNotBlank(text)) { String[] split = text.split(","); for (String s : split) { Map apply = fun.apply(s); list.add(apply); } } return list; } /** * 构建浏览按钮字段信息格式 * * @param text * @param browserType * @return */ private List> getBrowserArray(String text, String browserType) { List> list = new ArrayList<>(); if (StringUtils.isNotBlank(text)) { String[] split = text.split(","); for (String s : split) { Map map = new HashMap<>(2); if (StringUtils.isNotBlank(text)) { String id = ModeBrowserCommonInfo.getBrowserId(browserType, text); if (StringUtils.isNotBlank(id)) { map.put("id", id); map.put("name", text); } } list.add(map); } } return list; } /** * 构建学历字段信息 * * @param text * @return */ private Map getEducationLevelArray(String text) { Map map = new HashMap<>(2); if (StringUtils.isNotBlank(text)) { String id = ModeBrowserCommonInfo.getEducationLevelId(text); if (StringUtils.isNotBlank(id)) { map.put("id", id); map.put("name", text); } } return map; } }