package com.engine.recruit.service.impl; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.engine.core.exception.ECException; import com.engine.core.impl.Service; import com.engine.recruit.conn.ApplicantCommonInfo; import com.engine.recruit.conn.ModeBrowserCommonInfo; import com.engine.recruit.exception.CustomizeRunTimeException; import com.engine.recruit.service.ResumeRecognitionService; import com.engine.recruit.util.HttpUtils; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; 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.file.ImageFileManager; import weaver.formmode.recruit.modeexpand.util.RecruitModeUtil; import weaver.general.BaseBean; import weaver.general.Util; import java.io.InputStream; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; /** * @author:dxfeng * @createTime: 2023/12/12 * @version: 1.0 */ public class ResumeRecognitionServiceImpl extends Service implements ResumeRecognitionService { /** * 简历识别类型 */ private static final String OCR_TYPE = RecruitModeUtil.getRecruitPropValue("OCR_TYPE"); @Override public Map resumeUpload(Map param) { Map returnMap = new HashMap<>(16); returnMap.put("isOcr", true); if (StringUtils.isBlank(OCR_TYPE)) { returnMap.put("isOcr", false); return returnMap; } String resumeId = Util.null2String(param.get("resumeId")); int imageField = ApplicantCommonInfo.getImageFieldByDocId(resumeId); if (-1 == imageField) { throw new CustomizeRunTimeException("原始简历文件获取失败,请重新上传原始简历"); } // 调用千里聆服务 if ("1".equals(OCR_TYPE)) { qllResumeUpload(imageField, returnMap); } // 调用阿里云服务 if ("2".equals(OCR_TYPE)) { alyResumeUpload(imageField, returnMap); } return returnMap; } /** * @param resumeId 简历ID * @param returnMap 响应集合 */ private void qllResumeUpload(int resumeId, Map returnMap) { String response = doQllPost(resumeId); if (StringUtils.isBlank(response)) { throw new CustomizeRunTimeException("千里聆接口调用失败,响应结果为空"); } JSONObject all = JSONObject.parseObject(response); if (!all.getBoolean("isSuccess")) { throw new CustomizeRunTimeException("千里聆接口调用失败,接口不通"); } JSONObject resultall = all.getJSONObject("data"); String status = resultall.getString("state"); if ("fail".equals(status)) { throw new CustomizeRunTimeException("调用千里聆接口失败,失败原因:" + resultall.getString("info")); } JSONObject result = resultall.getJSONObject("result"); Map dataMap = parseQllJsonToMap(result); returnMap.put("data", dataMap); } /** * @param resumeId 简历ID * @param returnMap 响应集合 */ private void alyResumeUpload(int resumeId, Map returnMap) { String response = doAlyPost(resumeId); JSONObject all = JSONObject.parseObject(response); String errorMsg = all.getString("error_msg"); if (!"成功".equals(errorMsg)) { throw new CustomizeRunTimeException("阿里云简历识别失败"); } JSONObject parseObject = all.getJSONObject("data").getJSONObject("cv_parse"); Map saveResume = parseAlyJsonToMap(parseObject); returnMap.put("data", saveResume); } /** * 千里聆解析字段内容处理 * * @param obj * @return */ private Map parseAlyJsonToMap(JSONObject obj) { JSONObject basicInfo = obj.getJSONObject("basic_info"); JSONObject contact = obj.getJSONObject("contact"); JSONObject jobObjective = obj.getJSONObject("job_objective"); Map dataMap = new HashMap<>(16); // 姓名 String xm = basicInfo.getString("name"); dataMap.put("xm", xm); // 电子邮箱 String dzyx = contact.getString("email"); dataMap.put("dzyx", dzyx); // 手机号码 String sjhm = contact.getString("mobile"); dataMap.put("sjhm", sjhm); // 年龄 String nl = basicInfo.getString("age"); dataMap.put("nl", nl); // 出生日期 String csrq = basicInfo.getString("birthday"); dataMap.put("csrq", parseDateObject(csrq)); // 民族 String mz = basicInfo.getString("nation"); dataMap.put("mz", mz); // 性别 String xb = "女".equals(basicInfo.getString("gender")) ? "1" : "0"; dataMap.put("xb", xb); // 体重(KG) // 身高(CM) // 籍贯 dataMap.put("jg", basicInfo.getString("native_place")); // 婚姻状况 // 当前所在地 dataMap.put("jzd", basicInfo.getString("extract_location")); // 政治面貌 // 在职状态 // 工作经验 dataMap.put("gzjy", basicInfo.getString("work_years_original")); // 最高学位 // 最高学历 dataMap.put("zgxl", getBrowserArray(basicInfo.getString("highest_degree"), this::getEducationLevelArray)); // 毕业院校 String byyx = basicInfo.getString("graduate_school"); dataMap.put("byyx", byyx); // 期望税前月薪(K) String qwxz = jobObjective.getString("expect_salary_upper"); dataMap.put("qwxz", qwxz); // 现税前月薪(K) // 自我评价 String zwpj = obj.getString("self_evaluate"); dataMap.put("zwpj", zwpj); // 明细表数据 // 教育经历 JSONArray jyjl = obj.getJSONArray("educations"); 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 startTime = o.getString("start_time"); String endTime = o.getString("end_time"); String studyDate = startTime + "-" + endTime; Map studyMap = new HashMap<>(getDateRange(studyDate, true)); studyMap.put("xxmc", o.getString("school")); studyMap.put("xl", getBrowserArray(o.getString("degree"), this::getEducationLevelArray)); studyMap.put("zy", o.getString("major")); studyList.add(studyMap); } } dataMap.put("jyjl", studyList); // 工作经历 JSONArray gzjl = obj.getJSONArray("occupations"); 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 startTime = o.getString("start_time"); String endTime = o.getString("end_time"); String workDate = startTime + "-" + endTime; Map workMap = new HashMap<>(getDateRange(workDate, false)); workMap.put("gsmc", o.getString("company")); workMap.put("gw", o.getString("title")); workMap.put("gzzz", o.getString("desc")); workList.add(workMap); } } dataMap.put("gzjl", workList); return dataMap; } /** * 千里聆解析字段内容处理 * * @param obj * @return */ private Map parseQllJsonToMap(JSONObject obj) { Map dataMap = new HashMap<>(16); // 姓名 String xm = parseArray(obj.getJSONArray("姓名")); dataMap.put("xm", xm); // 电子邮箱 String dzyx = parseArray(obj.getJSONArray("电子邮箱")); dataMap.put("dzyx", dzyx); // 手机号码 String sjhm = parseArray(obj.getJSONArray("手机号")); dataMap.put("sjhm", sjhm); // 年龄 String nl = parseArray(obj.getJSONArray("年龄")); dataMap.put("nl", nl); // 出生日期 String csrq = parseArray(obj.getJSONArray("出生日期")); dataMap.put("csrq", parseDateObject(csrq)); // 民族 // 性别 String xb = "男".equals(parseArray(obj.getJSONArray("性别"))) ? "0" : "1"; dataMap.put("xb", xb); // 体重(KG) // 身高(CM) // 籍贯 dataMap.put("jg", parseArray(obj.getJSONArray("籍贯"))); // 婚姻状况 // 当前所在地 dataMap.put("jzd", parseArray(obj.getJSONArray("现居住地"))); // 政治面貌 // 在职状态 // 工作经验 dataMap.put("gzjy", parseArray(obj.getJSONArray("工作经验"))); // 最高学位 dataMap.put("zgxw", getBrowserArray(parseArray(obj.getJSONArray("最高学位")), this::getDegreeArray)); // 最高学历 dataMap.put("zgxl", getBrowserArray(parseArray(obj.getJSONArray("最高学历")), this::getEducationLevelArray)); // 专业 String zy = parseArray(obj.getJSONArray("专业")); dataMap.put("zy", zy); // 毕业院校 String byyx = parseArray(obj.getJSONArray("毕业院校")); dataMap.put("byyx", byyx); // 期望税前月薪(K) String qwxz = parseArray(obj.getJSONArray("期望薪资")); dataMap.put("qwxz", qwxz); // 现税前月薪(K) // 自我评价 String zwpj = parseArray(obj.getJSONArray("个人评价")); dataMap.put("zwpj", zwpj); // 民族 dataMap.put("mz", parseArray(obj.getJSONArray("民族"))); // 明细表数据 // 教育经历 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("就读时期"); Map studyMap = new HashMap<>(getDateRange(studyDate, true)); studyMap.put("xxmc", o.getString("院校")); studyMap.put("xl", getBrowserArray(o.getString("学历"), this::getEducationLevelArray)); studyMap.put("zy", o.getString("专业")); 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("工作时间"); Map workMap = new HashMap<>(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); // 项目经验 // 语言能力 // 目前表单暂无字段 dataMap.put("wx", parseArray(obj.getJSONArray("微信"))); dataMap.put("qq", parseArray(obj.getJSONArray("QQ"))); dataMap.put("ah", parseArray(obj.getJSONArray("爱好"))); dataMap.put("bysj", parseArray(obj.getJSONArray("毕业时间"))); dataMap.put("zyjn", parseArray(obj.getJSONArray("专业技能"))); dataMap.put("sxjl", parseArray(obj.getJSONArray("实习经历"))); dataMap.put("yysp", parseArray(obj.getJSONArray("英语水平"))); dataMap.put("jnzs", parseArray(obj.getJSONArray("技能证书"))); dataMap.put("xyjl", parseArray(obj.getJSONArray("校园经历"))); dataMap.put("qwcsgw", parseArray(obj.getJSONArray("期望从事岗位"))); dataMap.put("qwgzdd", parseArray(obj.getJSONArray("期望工作地点"))); dataMap.put("xmjl", parseArray(obj.getJSONArray("项目经历"))); return dataMap; } /** * @param resumeId * @return */ private String doQllPost(int resumeId) { BaseBean bean = new BaseBean(); String url = bean.getPropValue("resume_qianliling", "qianlilingurl"); String appId = bean.getPropValue("resume_qianliling", "appId"); String appSecret = bean.getPropValue("resume_qianliling", "appSecret"); 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.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 resumeId * @return */ private String doAlyPost(int resumeId) { try { BaseBean bean = new BaseBean(); String parseHost = bean.getPropValue("youyun", "host"); String path = bean.getPropValue("youyun", "path"); String method = bean.getPropValue("youyun", "method"); String appcode = bean.getPropValue("youyun", "appcode"); ImageFileManager manager = new ImageFileManager(); manager.getImageFileInfoById(resumeId); Map headers = new HashMap<>(2); headers.put("Authorization", "APPCODE " + appcode); headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); Map queryMap = new HashMap<>(1); Map bodyMap = new HashMap<>(2); InputStream inputStream = manager.getInputStream(); byte[] bytes = IOUtils.toByteArray(inputStream); String base64 = Base64.encodeBase64String(bytes); bodyMap.put("content", base64); String fileName = manager.getImageFileName(); String suffixType = fileName.substring(fileName.lastIndexOf('.') + 1); bodyMap.put("ext", suffixType); HttpResponse response = HttpUtils.doPost(parseHost, path, method, headers, queryMap, bodyMap); return EntityUtils.toString(response.getEntity()); } catch (Exception var10) { throw new ECException("简历解析失败!详情:" + var10.getMessage()); } } /** * 千里聆签名 * * @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 ar * @return */ private String parseArray(JSONArray ar) { StringBuilder rs = new StringBuilder(); if (ar != null && ar.size() > 0) { for (int i = 0; i < ar.size(); ++i) { if (i == ar.size() - 1) { rs.append(ar.get(i)); } else { rs.append(ar.get(i)).append(","); } } } return rs.toString(); } /** * 转换日期对象 * * @param value * @return */ private String parseDateObject(String value) { DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); sdf.setLenient(false); try { sdf.parse(value); } catch (ParseException e) { value = ""; } return value; } /** * 获取开始时间和结束时间 * * @param date * @return */ private Map getDateRange(String date, boolean isStudy) { Map dataRangeMap = new HashMap<>(2); String[] split = date.split("-"); if (split.length == 2) { String start = getFormatDate(split[0]); String end = getFormatDate(split[1]); if (start.length() == 4) { if (isStudy) { start += "-09-01"; } else { start = ""; } } if (end.length() == 4) { if (isStudy) { end += "-07-01"; } else { end = ""; } } dataRangeMap.put("kssj", start); 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 * @return */ private Map getDegreeArray(String text) { Map map = new HashMap<>(2); if (StringUtils.isNotBlank(text)) { String id = ModeBrowserCommonInfo.getDegreeId(text); if (StringUtils.isNotBlank(id)) { map.put("id", id); map.put("name", text); } } return map; } /** * 构建浏览按钮字段信息格式 * * @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 * @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; } }