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.FileUtils; 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.File; 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<>(); 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 error_msg = all.getString("error_msg"); if (!"成功".equals(error_msg)) { throw new CustomizeRunTimeException("阿里云简历识别失败"); } Map saveResume = new HashMap<>(); JSONObject cv_parse = all.getJSONObject("data").getJSONObject("cv_parse"); saveResume.putAll(buildBasicInfo(cv_parse.getJSONObject("basic_info"))); saveResume.putAll(buildEducations(getJsonArrayFirstIfPresent(cv_parse.getJSONArray("educations")))); saveResume.putAll(buildContact(cv_parse.getJSONObject("contact"))); saveResume.putAll(buildJobObjective(cv_parse.getJSONObject("job_objective"))); // TODO 阿里云解析内容处理 returnMap.put("data", saveResume); } /** * 千里聆解析字段内容处理 * * @param obj * @return */ private Map parseQllJsonToMap(JSONObject obj) { Map dataMap = new HashMap<>(); // 姓名 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); // 明细表数据 // 教育经历 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)); 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)); workMap.put("gsmc", o.getString("工作单位")); workMap.put("gw", o.getString("工作岗位")); workMap.put("gzzz", o.getString("工作内容")); workList.add(workMap); } } dataMap.put("gzjl", workList); // 项目经验 // 语言能力 // TODO dataMap.put("mz", parseArray(obj.getJSONArray("民族"))); 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; } private Map buildBasicInfo(JSONObject basicInfo) { Map basic = new HashMap<>(); if (basicInfo.containsKey("name")) { String jzd; basic.put("xm", Util.null2String(basicInfo.getString("name"))); basic.put("nl", Util.null2String(basicInfo.getInteger("age"))); if ("男".equals(Util.null2String(basicInfo.getString("gender")))) { basic.put("xb", "0"); } else if ("女".equals(Util.null2String(basicInfo.getString("gender")))) { basic.put("xb", "1"); } jzd = basicInfo.getJSONObject("location").getString("province") + basicInfo.getJSONObject("location").getString("city"); basic.put("jzd", Util.null2String(jzd)); } return basic; } private Map buildEducations(JSONObject educations) { Map major = new HashMap<>(); if (educations != null) { if (educations.containsKey("major")) { major.put("zy", Util.null2String(educations.getString("major"))); } } return major; } private Map buildContact(JSONObject contact) { Map mobile = new HashMap<>(); if (contact.containsKey("mobile")) { mobile.put("wx", Util.null2String(contact.getString("mobile"))); mobile.put("sjhm", Util.null2String(contact.getString("mobile"))); mobile.put("qq", Util.null2String(contact.getString("qq"))); mobile.put("email", Util.null2String(contact.getString("email"))); } return mobile; } private Map buildJobObjective(JSONObject job_objective) { Map expect_salary = new HashMap<>(); if (job_objective != null) { if (job_objective.containsKey("expect_salary")) { expect_salary.put("qwxz", Util.null2String(job_objective.getString("expect_salary"))); } } return expect_salary; } private JSONObject getJsonArrayFirstIfPresent(JSONArray jsonArray) { return jsonArray.size() > 0 ? jsonArray.getJSONObject(0) : null; } /** * @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(); // TODO 多文件类型兼容 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 PARSE_HOST = 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); String filePath = manager.getFileRealPath(); File file = new File(filePath); Map headers = new HashMap<>(); headers.put("Authorization", "APPCODE " + APPCODE); headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); Map queryMap = new HashMap<>(); Map bodyMap = new HashMap<>(); byte[] bytes = FileUtils.readFileToByteArray(file); String base64 = Base64.encodeBase64String(bytes); bodyMap.put("content", base64); bodyMap.put("ext", "doc"); HttpResponse response = HttpUtils.doPost(PARSE_HOST, 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) { Map dataRangeMap = new HashMap<>(2); String[] split = date.split("-"); if (split.length == 2) { String start = getFormatDate(split[0]); String end = getFormatDate(split[1]); dataRangeMap.put("kssj", start); dataRangeMap.put("jssj", end); } return dataRangeMap; } /** * 获取yyyy-MM-dd时间格式日期 * * @param dateStr * @return */ private String getFormatDate(String dateStr) { dateStr = dateStr.replace(".", "-"); if (dateStr.length() == 7) { return dateStr + "-01"; } else if (dateStr.length() == 10) { return dateStr; } return ""; } /** * 构建学位字段信息 * * @param text * @return */ private Map getDegreeArray(String text) { Map map = new HashMap<>(); 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<>(); if (StringUtils.isNotBlank(text)) { String id = ModeBrowserCommonInfo.getEducationLevelId(text); if (StringUtils.isNotBlank(id)) { map.put("id", id); map.put("name", text); } } return map; } }