| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- package com.kingdee.eas.custom.recuritment.service;
- import com.alibaba.fastjson.JSONArray;
- import com.alibaba.fastjson.JSONObject;
- import com.kingdee.bos.BOSException;
- import com.kingdee.bos.Context;
- import com.kingdee.bos.bsf.service.app.IHRMsfService;
- import com.kingdee.bos.metadata.entity.EntityViewInfo;
- import com.kingdee.bos.metadata.entity.FilterInfo;
- import com.kingdee.bos.metadata.entity.FilterItemCollection;
- import com.kingdee.bos.metadata.entity.FilterItemInfo;
- import com.kingdee.bos.metadata.entity.SelectorItemCollection;
- import com.kingdee.bos.metadata.entity.SelectorItemInfo;
- import com.kingdee.bos.metadata.query.util.CompareType;
- import com.kingdee.eas.basedata.person.IPerson;
- import com.kingdee.eas.basedata.person.PersonCollection;
- import com.kingdee.eas.basedata.person.PersonFactory;
- import com.kingdee.eas.common.EASBizException;
- import com.kingdee.eas.custom.recuritment.utils.BeisenRecruitOwnerApiHelper;
- import com.kingdee.eas.custom.recuritment.ApplicantBeisenCollection;
- import com.kingdee.eas.custom.recuritment.ApplicantBeisenFactory;
- import com.kingdee.eas.custom.recuritment.ApplicantBeisenInfo;
- import com.kingdee.eas.custom.recuritment.IApplicantBeisen;
- import com.kingdee.eas.hr.ats.AtsUtil;
- import com.kingdee.util.StringUtils;
- import org.apache.log4j.Logger;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- /**
- * 同步北森应聘者(ApplicantBeisen)招聘负责人 OSF 服务。
- * 业务链路:申请ID → 北森申请(jobId) → 北森职位(hrDutyUser) → 北森员工(staffCode) → sHR人员(recruitOwner)。
- * 待同步判定:recruitOwner(招聘负责人)为空才同步;已有负责人则跳过。
- * 同步结果写入 syncRecruit,成功为「同步成功」,失败为具体原因。
- * OSF 服务名:syncRecruitOwnerService;入参 billIds 为空时走定时扫描。
- *
- * @author qingwu
- */
- public class SyncRecruitOwnerService implements IHRMsfService {
- /** 日志记录器 */
- private static final Logger logger = Logger.getLogger(SyncRecruitOwnerService.class);
- /** 同步成功固定文案,写入 syncRecruit 字段,禁止随意修改 */
- private static final String SYNC_SUCCESS = "同步成功";
- /** 北森批量接口每批条数,避免单次请求过多超时或限流 */
- private static final int BATCH_SIZE = 70;
- /** 北森申请/职位/员工接口封装 */
- private final BeisenRecruitOwnerApiHelper apiHelper = new BeisenRecruitOwnerApiHelper();
- /**
- * OSF 入口:加载待同步单据,分批调用北森并回写招聘负责人。
- *
- * @param ctx BOS 上下文
- * @param map 入参,key 为 billIds(可选,逗号分隔内码)
- * @return code、message、successSize、errorSize、listMsg
- */
- @Override
- public Object process(Context ctx, Map<String, Object> map) throws EASBizException, BOSException {
- // billIds 为空=定时扫描,非空=界面指定单据
- String billIds = map.get("billIds") == null ? null : map.get("billIds").toString();
- logger.error("syncRecruitOwnerService billIds:" + billIds);
- Map<String, Object> result = new HashMap<String, Object>();
- List<Map<String, Object>> listMsg = new ArrayList<Map<String, Object>>();
- int successSize = 0;
- int errorSize = 0;
- IApplicantBeisen applicantBeisen = ApplicantBeisenFactory.getLocalInstance(ctx);
- ApplicantBeisenCollection collection = loadCollection(applicantBeisen, billIds);
- // 排除已有招聘负责人及历史数据,避免重复调北森
- List<ApplicantBeisenInfo> todoList = new ArrayList<ApplicantBeisenInfo>();
- for (int i = 0; i < collection.size(); i++) {
- ApplicantBeisenInfo info = collection.get(i);
- if (!shouldSkip(info)) {
- todoList.add(info);
- }
- }
- // 跨批次缓存:同一 originalId 只查一次员工接口
- Map<String, String> staffCodeCache = new HashMap<String, String>();
- Map<String, JSONObject> staffJsonCache = new HashMap<String, JSONObject>();
- for (int i = 0; i < todoList.size(); i += BATCH_SIZE) {
- int end = Math.min(i + BATCH_SIZE, todoList.size());
- List<ApplicantBeisenInfo> batch = todoList.subList(i, end);
- int[] counts = syncBatch(ctx, applicantBeisen, batch, staffCodeCache, staffJsonCache, listMsg);
- successSize += counts[0];
- errorSize += counts[1];
- }
- result.put("code", 200);
- result.put("message", "请求成功");
- result.put("successSize", successSize);
- result.put("errorSize", errorSize);
- result.put("listMsg", listMsg);
- return result;
- }
- /**
- * 处理一批单据:批量查申请与职位,再逐条解析人员并回写。
- *
- * @return int[2],[0]成功条数,[1]失败条数
- */
- private int[] syncBatch(Context ctx, IApplicantBeisen applicantBeisen, List<ApplicantBeisenInfo> batch,
- Map<String, String> staffCodeCache, Map<String, JSONObject> staffJsonCache,
- List<Map<String, Object>> listMsg) {
- int successSize = 0;
- int errorSize = 0;
- Map<String, ApplicantBeisenInfo> applyInfoMap = new LinkedHashMap<String, ApplicantBeisenInfo>();
- List<String> applyIds = new ArrayList<String>();
- // 校验 applyId,无效直接回写失败
- for (ApplicantBeisenInfo info : batch) {
- String billId = info.getId().toString();
- String applyId = info.getApplyId();
- if (StringUtils.isEmpty(applyId)) {
- errorSize++;
- saveResultQuiet(applicantBeisen, info, "申请id为空");
- listMsg.add(buildMsg(billId, applyId, 500, "申请id为空"));
- continue;
- }
- applyIds.add(applyId);
- applyInfoMap.put(applyId, info);
- }
- if (applyIds.isEmpty()) {
- return new int[]{successSize, errorSize};
- }
- Map<String, String> applyIdToJobId = new HashMap<String, String>();
- JSONObject batchApplyJson = null;
- JSONObject batchJobJson = null;
- try {
- batchApplyJson = apiHelper.getApplyListByApplyIds(applyIds);
- if (batchApplyJson.getIntValue("code") != 200) {
- // 整批申请接口失败,本批统一记同一原因
- String msg = "根据申请ID获取申请信息失败:" + batchApplyJson.getString("message");
- for (String applyId : applyIds) {
- ApplicantBeisenInfo info = applyInfoMap.get(applyId);
- errorSize++;
- saveResultQuiet(applicantBeisen, info, msg);
- listMsg.add(buildMsg(info.getId().toString(), applyId, 500, msg, batchApplyJson));
- }
- return new int[]{successSize, errorSize};
- }
- JSONArray applyList = batchApplyJson.getJSONArray("data");
- if (applyList != null) {
- for (int j = 0; j < applyList.size(); j++) {
- JSONObject item = applyList.getJSONObject(j);
- applyIdToJobId.put(item.getString("applyId"), item.getString("jobId"));
- }
- }
- } catch (Exception e) {
- logger.error("批量获取申请信息失败", e);
- for (String applyId : applyIds) {
- ApplicantBeisenInfo info = applyInfoMap.get(applyId);
- errorSize++;
- saveResultQuiet(applicantBeisen, info, e.getMessage());
- listMsg.add(buildMsg(info.getId().toString(), applyId, 500, e.getMessage()));
- }
- return new int[]{successSize, errorSize};
- }
- Set<String> jobIdSet = new HashSet<String>(applyIdToJobId.values());
- jobIdSet.remove(null);
- Map<String, String> jobIdToHrDutyUser = new HashMap<String, String>();
- try {
- batchJobJson = batchLoadJobDutyUser(new ArrayList<String>(jobIdSet), jobIdToHrDutyUser);
- } catch (Exception e) {
- logger.error("批量获取职位信息失败", e);
- for (String applyId : applyIds) {
- ApplicantBeisenInfo info = applyInfoMap.get(applyId);
- errorSize++;
- saveResultQuiet(applicantBeisen, info, e.getMessage());
- listMsg.add(buildMsg(info.getId().toString(), applyId, 500, e.getMessage()));
- }
- return new int[]{successSize, errorSize};
- }
- // 逐条解析员工、匹配 sHR 人员并回写
- for (String applyId : applyIds) {
- ApplicantBeisenInfo info = applyInfoMap.get(applyId);
- String billId = info.getId().toString();
- try {
- Map<String, Object> syncResult = resolveAndSave(ctx, applicantBeisen, info, applyIdToJobId,
- jobIdToHrDutyUser, staffCodeCache, staffJsonCache, batchApplyJson, batchJobJson);
- String msg = (String) syncResult.get("message");
- JSONObject apiData = (JSONObject) syncResult.get("apiData");
- if (SYNC_SUCCESS.equals(msg)) {
- successSize++;
- listMsg.add(buildMsg(billId, applyId, 200, msg, apiData));
- } else {
- errorSize++;
- listMsg.add(buildMsg(billId, applyId, 500, msg, apiData));
- }
- } catch (Exception e) {
- logger.error("同步招聘负责人失败, billId=" + billId, e);
- errorSize++;
- saveResultQuiet(applicantBeisen, info, e.getMessage());
- listMsg.add(buildMsg(billId, applyId, 500, e.getMessage()));
- }
- }
- return new int[]{successSize, errorSize};
- }
- /**
- * 批量查职位,填充 jobId → hrDutyUser(北森负责人 originalId)。
- */
- private JSONObject batchLoadJobDutyUser(List<String> jobIds, Map<String, String> jobIdToHrDutyUser) throws Exception {
- if (jobIds.isEmpty()) {
- return new JSONObject();
- }
- JSONObject jobJson = apiHelper.getJobListByIds(jobIds);
- if (jobJson.getIntValue("code") != 200) {
- throw new BOSException("根据职位Id获取职位列表失败:" + jobJson.getString("message"));
- }
- JSONArray jobList = jobJson.getJSONArray("data");
- if (jobList == null) {
- return jobJson;
- }
- for (int i = 0; i < jobList.size(); i++) {
- JSONObject item = jobList.getJSONObject(i);
- String jobId = item.getString("jobId");
- // 兼容 jobId 在 jobLite.jobGuid 中的返回结构
- if (StringUtils.isEmpty(jobId) && item.getJSONObject("jobLite") != null) {
- jobId = item.getJSONObject("jobLite").getString("jobGuid");
- }
- // 兜底:按请求顺序与入参对齐
- if (StringUtils.isEmpty(jobId) && i < jobIds.size()) {
- jobId = jobIds.get(i);
- }
- Object hrDutyUser = item.get("hrDutyUser");
- if (hrDutyUser != null) {
- jobIdToHrDutyUser.put(jobId, hrDutyUser.toString());
- }
- }
- return jobJson;
- }
- /**
- * 单条解析并回写:applyId → jobId → hrDutyUser → staffCode → sHR Person。
- *
- * @return message(同步结果)、apiData(北森中间数据快照)
- */
- private Map<String, Object> resolveAndSave(Context ctx, IApplicantBeisen applicantBeisen, ApplicantBeisenInfo info,
- Map<String, String> applyIdToJobId, Map<String, String> jobIdToHrDutyUser,
- Map<String, String> staffCodeCache, Map<String, JSONObject> staffJsonCache,
- JSONObject batchApplyJson, JSONObject batchJobJson) throws Exception {
- Map<String, Object> result = new HashMap<String, Object>();
- JSONObject apiData = new JSONObject();
- apiData.put("applyJson", batchApplyJson);
- apiData.put("jobJson", batchJobJson);
- String applyId = info.getApplyId();
- String jobId = applyIdToJobId.get(applyId);
- apiData.put("jobId", jobId);
- if (StringUtils.isEmpty(jobId)) {
- String msg = "根据申请ID未查询到申请信息或jobId为空";
- saveResult(applicantBeisen, info, msg);
- result.put("message", msg);
- result.put("apiData", apiData);
- return result;
- }
- String hrDutyUser = jobIdToHrDutyUser.get(jobId);
- apiData.put("hrDutyUser", hrDutyUser);
- if (StringUtils.isEmpty(hrDutyUser)) {
- String msg = "职位信息中hrDutyUser为空";
- saveResult(applicantBeisen, info, msg);
- result.put("message", msg);
- result.put("apiData", apiData);
- return result;
- }
- JSONObject staffJson = staffJsonCache.get(hrDutyUser);
- if (staffJson == null) {
- staffJson = apiHelper.getStaffByOriginalId(hrDutyUser);
- staffJsonCache.put(hrDutyUser, staffJson);
- }
- apiData.put("staffJson", staffJson);
- if (staffJson.getIntValue("code") != 200) {
- String msg = "获取员工信息失败:" + staffJson.getString("message");
- staffCodeCache.put(hrDutyUser, ""); // 标记已失败,避免同批重复请求
- saveResult(applicantBeisen, info, msg);
- result.put("message", msg);
- result.put("apiData", apiData);
- return result;
- }
- String staffCode = staffCodeCache.get(hrDutyUser);
- if (staffCode == null) {
- staffCode = BeisenRecruitOwnerApiHelper.parseStaffCode(staffJson);
- staffCodeCache.put(hrDutyUser, staffCode == null ? "" : staffCode);
- }
- apiData.put("staffCode", staffCode);
- if (StringUtils.isEmpty(staffCode)) {
- String msg = "员工信息中staffCode为空,originalId=" + hrDutyUser;
- saveResult(applicantBeisen, info, msg);
- result.put("message", msg);
- result.put("apiData", apiData);
- return result;
- }
- IPerson personIns = PersonFactory.getLocalInstance(ctx);
- PersonCollection persons = personIns.getPersonCollection("where number='" + staffCode + "'");
- if (persons.isEmpty()) {
- String msg = "sHR未找到工号为[" + staffCode + "]的人员";
- saveResult(applicantBeisen, info, msg);
- result.put("message", msg);
- result.put("apiData", apiData);
- return result;
- }
- info.put("recruitOwner", persons.get(0));
- saveResult(applicantBeisen, info, SYNC_SUCCESS);
- result.put("message", SYNC_SUCCESS);
- result.put("apiData", apiData);
- return result;
- }
- /** 按 billIds 或定时规则加载待同步集合 */
- private ApplicantBeisenCollection loadCollection(IApplicantBeisen applicantBeisen, String billIds)
- throws BOSException, EASBizException {
- FilterInfo filterInfo = buildFilterInfo(billIds);
- EntityViewInfo viewInfo = EntityViewInfo.getInstance(filterInfo, null, null);
- return applicantBeisen.getApplicantBeisenCollection(viewInfo);
- }
- /**
- * 构建查询条件:指定 billIds 时按 id 过滤;为空时扫 recruitOwner 为空且非历史数据。
- */
- private FilterInfo buildFilterInfo(String billIds) throws BOSException {
- FilterInfo filterInfo = new FilterInfo();
- FilterItemCollection filterItems = filterInfo.getFilterItems();
- if (!StringUtils.isEmpty(billIds)) {
- if (billIds.contains("'")) {
- billIds = billIds.replace("'", "");
- }
- filterItems.add(new FilterItemInfo("id", AtsUtil.toSet(billIds), CompareType.INCLUDE));
- return filterInfo;
- }
- try {
- // recruitOwner IS NULL:招聘负责人为空才纳入定时同步
- filterItems.add(new FilterItemInfo("recruitOwner", null, CompareType.EQUALS));
- // isHistoricalData IS NULL OR = 0:排除历史归档数据
- FilterInfo histOrFilter = new FilterInfo();
- histOrFilter.getFilterItems().add(new FilterItemInfo("isHistoricalData", null, CompareType.EQUALS));
- FilterInfo histZero = new FilterInfo();
- histZero.getFilterItems().add(new FilterItemInfo("isHistoricalData", Integer.valueOf(0), CompareType.EQUALS));
- histOrFilter.mergeFilter(histZero, "OR");
- filterInfo.mergeFilter(histOrFilter, "AND");
- } catch (Exception e) {
- throw new BOSException(e);
- }
- return filterInfo;
- }
- /** 已有招聘负责人或历史数据则跳过,避免重复同步 */
- private boolean shouldSkip(ApplicantBeisenInfo info) {
- if (!isRecruitOwnerEmpty(info)) {
- return true;
- }
- return isHistoricalData(info.get("isHistoricalData"));
- }
- /** 判断招聘负责人是否为空,为空表示待同步 */
- private boolean isRecruitOwnerEmpty(ApplicantBeisenInfo info) {
- return info.get("recruitOwner") == null;
- }
- /** 兼容 Boolean true 与 "1" 两种历史数据存储 */
- private boolean isHistoricalData(Object value) {
- if (value == null) {
- return false;
- }
- if (value instanceof Boolean) {
- return Boolean.TRUE.equals(value);
- }
- return "1".equals(String.valueOf(value));
- }
- /**
- * 回写 syncRecruit;成功时同时回写 recruitOwner。
- * 失败时不更新 recruitOwner,保留原值。
- */
- private void saveResult(IApplicantBeisen applicantBeisen, ApplicantBeisenInfo info, String message)
- throws BOSException, EASBizException {
- info.put("syncRecruit", message);
- SelectorItemCollection selector = new SelectorItemCollection();
- selector.add(new SelectorItemInfo("syncRecruit"));
- if (SYNC_SUCCESS.equals(message)) {
- selector.add(new SelectorItemInfo("recruitOwner"));
- }
- applicantBeisen.updatePartial(info, selector);
- }
- /** 静默回写,失败只打日志不中断整批 */
- private void saveResultQuiet(IApplicantBeisen applicantBeisen, ApplicantBeisenInfo info, String message) {
- try {
- saveResult(applicantBeisen, info, message);
- } catch (Exception e) {
- logger.error("回写syncRecruit失败, billId=" + info.getId(), e);
- }
- }
- /** 组装 OSF 逐条返回明细 */
- private Map<String, Object> buildMsg(String billId, String applyId, int code, String message) {
- return buildMsg(billId, applyId, code, message, null);
- }
- /** 组装 OSF 逐条返回明细,可附带北森 apiData */
- private Map<String, Object> buildMsg(String billId, String applyId, int code, String message, JSONObject apiData) {
- Map<String, Object> item = new HashMap<String, Object>();
- item.put("billId", billId);
- item.put("applyId", applyId);
- item.put("code", code);
- item.put("message", message);
- if (apiData != null) {
- item.put("apiData", apiData);
- }
- return item;
- }
- }
|