泛微OA-离职人员流程转移接口

泛微OA-离职人员流程转移接口

前言:对于离职人员,走了离职交接流程可以执行 action 接口自动创建流程代理,进行代理的流程只能代理未来到达的流程,但是已到达的流程如果离职人在在职期间不处理,将一直卡在离职人那里。

一、突破点

可利用 hrmresource 表的 enddate 字段,筛选当日的离职人员。

  • 1、根据离职人员去查询他的离职交接流程,找到交接人。
  • 2、查询此人的待办流程(转发、传阅、申请节点、归档节点的流程可过滤)。
  • 3、通过 /api/hrm/permissiontoadjust/processData 接口批量转移流程至交接人。


tips:enddate 字段代表人员离职后 hr 系统关闭账号的日期,因此用该字段来作为筛选条件是安全的。


二、流程转移代码

ProcessTransfer.java

public class ProcessTransfer extends BaseCronJob {
    private static Logger logger = LoggerFactory.getLogger(ProcessTransfer.class);
    private static final String TABLE_NAME = "formtable_main_62";
    private static final String LOG_TABLE_NAME = "transfer_log"; // 日志表名
    private String workflowid; // 过滤的流程(某类流程不进行代理)

    @Override
    public void execute() {
        LocalDate currentDate = LocalDate.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String todayDate = currentDate.format(formatter);
        RecordSetDataSource rs = new RecordSetDataSource();
        // 1.查询今日离职人
        List<Integer> userids = this.queryResign(todayDate, rs);
        if (userids.isEmpty()) {
            logger.info("今日【" + todayDate + "】没有离职人,接口退出。");
            return;
        }
        JSONObject jsonObject = this.getToken(); // 优先获取token
        for (int i = 0; i < userids.size(); i++) {
            int userid = userids.get(i);
            // 2.查询离职人对应交接人
            int jjr = this.queryHanderOver(userid, rs);
            if (jjr == -1) {
                continue;
            }

            // 3.查询当前离职人待办流程(转发、传阅、创建节点、归档节点等已过滤)
            List<Integer> requestids = this.queryPendProcess(userid, rs);
            if (requestids.isEmpty()) {
                logger.info("用户【" + userid + "】名下暂无待办流程。");
                continue;
            }

            String requestidStr = requestids.toString().replaceAll("^\\[|\\]$", "");
            // 4.流程转移
            processTransfe(userid, jjr, requestidStr, jsonObject);
        }
    }

    /**
     * 汇总当日离职人id
     *
     * @return
     */
    private List<Integer> queryResign(String todayDate, RecordSetDataSource rs) {
        List<Integer> userids = new ArrayList<>();
        String sql = " select id from hrmresource where status in(4,5,6) and enddate ='" + todayDate + "'";
        try {
            rs.executeSql(sql);
            while (rs.next()) {
                userids.add(rs.getInt("id"));
            }
        } catch (Exception e) {
            logger.error("sql执行错误. sql: " + sql);
            throw new RuntimeException("processTransfer(流程转移)接口,查询当日离职人id异常:" + e.getMessage());
        }
        return userids;
    }

    /**
     * 待办流程
     *
     * @param userid
     * @return
     */
    private List<Integer> queryPendProcess(int userid, RecordSetDataSource rs) {
        List<Integer> requestids = new ArrayList<>();
        StringBuffer sql = new StringBuffer(" select t1.requestid from workflow_requestbase t1 ")
                .append(" inner join workflow_currentoperator t2 on t1.requestid = t2.requestid ")
                .append(" inner join workflow_base t3 on t1.workflowid = t3.id ")
                .append(" where (t1.deleted <> 1 or t1.deleted is null or t1.deleted='') ")
                .append(" and t2.userid= " + userid + " and t2.usertype=0 ")
                .append(" and ((t2.isremark=0 and (t2.takisremark is null or t2.takisremark=0 )) or t2.isremark =0) ")
                .append(" and (t1.deleted=0 or t1.deleted is null) and t2.islasttimes=1 and (isnull(t1.currentstatus,-1) = -1 or (isnull(t1.currentstatus,-1)=0 and t1.creater=" + userid + " )) ")
                .append(" and (t3.isvalid='1' or t3.isvalid='3') ") // 有效流程
                .append(" and t1.currentnodetype in(1,2) ") // 过滤创建、归档节点
                .append(" and t3.id not in(" +workflowid+ ")"); // 过滤的流程(如留言、系统异常工作流,可自定义传参)
        try {
            rs.executeSql(sql.toString());
            while (rs.next()) {
                requestids.add(rs.getInt("requestid"));
            }
        } catch (Exception e) {
            logger.error("sql执行错误. sql: " + sql);
            throw new RuntimeException("processTransfer(流程转移)接口,查询离职人待办流程异常:" + e.getMessage());
        }
        return requestids;
    }

    /**
     * 查询离职人对应交接人
     *
     * @param userid
     * @return
     */
    private int queryHanderOver(int userid, RecordSetDataSource rs) {
        String sql = " select top 1 jiaojr from " + TABLE_NAME + " where shenqr= " + userid + " order by shenqrq desc";
        int jjr = -1;
        try {
            rs.executeSql(sql);
            while (rs.next()) {
                String jiaojrArr[] = rs.getString("jiaojr").split(",");
                jjr = Integer.valueOf(jiaojrArr[0]);
            }
        } catch (Exception e) {
            logger.error("sql执行错误. sql: " + sql);
            throw new RuntimeException("processTransfer(流程转移)接口,查询离职人对应交接人异常:" + e.getMessage());
        }
        return jjr; // 交接人
    }

    /**
     * 流程转移接口
     *
     * @param userid     转移人
     * @param jjr        交接人
     * @param requests   转移的流程
     * @param jsonObject token 信息
     */
    private void processTransfe(int userid, int jjr, String requests, JSONObject jsonObject) {
        final String api = "/api/hrm/permissiontoadjust/processData";
        Map<String, Object> map = new HashMap<>();
        map.put("transferType", "resource");
        map.put("authorityTag", "transfer");
        map.put("fromid", userid);
        map.put("toid", jjr);
        map.put("T133IdStr", requests); // 转移的流程,requestid以逗号隔开
        String ipAddress = "";
        try {
            InetAddress localHost = InetAddress.getLocalHost();
            ipAddress = localHost.getHostAddress();
        } catch (Exception e) {
            logger.error("processTransfer(流程转移)接口,获取本机ip失败:" + e.getMessage());
            throw new RuntimeException("processTransfer(流程转移)接口,获取本机ip失败:" + e.getMessage());
        }
        if (ipAddress.equals("")) {
            ipAddress = "192.168.8.62"; // 正式环境可以替换为主节点:192.168.8.105
        }
        String encryptUserId = (String) jsonObject.get("encryptUserId");
        String token = (String) jsonObject.get("token");
        String appid = (String) jsonObject.get("appid");
        // 将 map 转换为 x-www-form-urlencoded 格式的字符串
        StringBuilder formData = new StringBuilder();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (formData.length() > 0) {
                formData.append("&");
            }
            formData.append(entry.getKey()).append("=").append(entry.getValue());
        }
        boolean isFinished = false;
        try {
            String resultJson = HttpRequest.post("http://" + ipAddress + ":8080" + api)
                    .header("Content-Type", "application/x-www-form-urlencoded")
                    .header("token", token)
                    .header("appid", appid)
                    .header("userid", encryptUserId)
                    .body(formData.toString()) // 使用 x-www-form-urlencoded 格式的字符串作为请求体
                    .execute().body();
            JSONObject rootObject = JSONObject.parseObject(resultJson);
            JSONObject messageObject = rootObject.getJSONObject("message"); // 嵌套的json
            String fromName = "";
            String toName = "";
            int succCount = -1;
            if (messageObject != null || !messageObject.isEmpty()) {
                isFinished = messageObject.getBooleanValue("isFinished"); // 获取isFinished
                fromName = messageObject.getString("from");
                toName = messageObject.getString("to");
                succCount = messageObject.getIntValue("succCount");
            }
            if (isFinished && !fromName.equals("") && !toName.equals("") && succCount != -1) {
                logger.info("流程转移成功: 【from:" + fromName + ",to:" + toName + "】,共:" + succCount + "条流程。");
            }
        } catch (Exception e) {
            logger.error("processTransfer(流程转移)接口,processTransfe失败!请联系管理员。 ");
            throw new RuntimeException("processTransfer(流程转移)接口,processTransfe失败!请联系管理员:" + e.getMessage());
        } finally {
            saveLog(requests, userid, jjr, isFinished);
        }
    }

    /**
     * 获取一次即可,接口执行就获取
     *
     * @return
     */
    private JSONObject getToken() {
        // 获取token
        LoginRegist loginRegist = new LoginRegist();
        Map<String, Object> tokenMap = new HashMap<>();
        tokenMap.put("workcode", "E1009132");
        return JSONObject.parseObject(loginRegist.ecPost(tokenMap));
    }

    private void saveLog(String requestids, int fromid, int toid, boolean isFinished) {
        requestids = requestids.replaceAll("\\s", ""); // 去空格
        BatchRecordSet brs = new BatchRecordSet();
        String[] dictKey = {"requestid", "from_id", "to_id", "operator", "transfer_time", "interface", "is_finished", "log"};
        List<String> keys = Arrays.asList(dictKey);
        String saveLogSql = DBUtil.makePrepareInsertSql(LOG_TABLE_NAME, keys); // 预编译sql
        List<Map<String, Object>> workflowList = new ArrayList<>();
        Map<String, Object> rowMap = null;
        String[] requestidArray = requestids.split(",");
        int index = 0;
        while (index < requestidArray.length) {
            rowMap = new HashMap<>();
            rowMap.put("requestid", requestidArray[index]); // requestid
            rowMap.put("from_id", String.valueOf(fromid)); // 流程原始审批人
            rowMap.put("to_id", String.valueOf(toid)); // 转移的人
            rowMap.put("operator", "schedule_interface"); // 默认传值计划任务接口
            LocalDateTime currentDateTime = LocalDateTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            String dateTime = currentDateTime.format(formatter);
            rowMap.put("transfer_time", dateTime);
            rowMap.put("interface", "com.comen.schedule.workflow.ProcessTransfer"); // 当前类路径
            rowMap.put("is_finished", String.valueOf(isFinished));
            rowMap.put("log", "人员离职后,已到达未处理流程转移。"); // 其他备注
            workflowList.add(rowMap);
            index++;
        }
        DBUtil.insertList2Table(brs, saveLogSql, keys, workflowList);
    }
}

DBUtil.java
public class DBUtil extends BaseBean {
	public static Log logger = LogFactory.getLog(DBUtil.class);

	/**
	* 获取自定义datasource的connection
	*
	* @param datasourceName
	*            数据源id,为"datasource"+数据源配置里面的数据源名称,
	*            比如配置了一个数据源名称为local,那么这个数据源id为:datasource.local
	* @return
	*/
	public static Connection getConnection(String datasourceName) {
		Connection conn = null;
		try {
			DataSource datasource = (DataSource) StaticObj.getServiceByFullname("datasource."+datasourceName, DataSource.class);  //获取数据源的信息
			conn = datasource.getConnection(); //和数据源取得连接
		} catch (Exception e) {
			e.printStackTrace();
			logger.error(e);
		}
		return conn;
	}

	/**
	 * 获取自定义datasource的connection
	 * OA的数据源
	 * @return
	 */
	public static Connection getConnection() {
		Connection conn = null;
		try {
			DataSource datasource = (DataSource) StaticObj.getServiceByFullname("datasource."+FinalString.DATASOURCE_OA, DataSource.class);  //获取数据源的信息
			conn = datasource.getConnection(); //和数据源取得连接
		} catch (Exception e) {
			e.printStackTrace();
			logger.error(e);
		}
		return conn;
	}
	
	/**
	* 关闭Connection
	*
	* @param conn
	*/
	public static void closeConnection(Connection conn) {
		if (conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
				logger.error(e);
			}
		}
	}

	/**
	 * 匹配sql
	 * @param sql 注意字符串要带''
	 * @param targets sql中对应的值
	 * @return 完整的sql
	 * @deprecated 该方法实际上只是把?替换成参数组装sql,既没有事务批处理的高效,也不能防止sql注入,建议使用void weaver.conn.BatchRecordSet.executeBatchSql(String arg0, List<List<Object>> arg1)替代。
	 */
	public static String prepareSQL(String sql, String... targets){
			if(null == targets || targets.length==0){
				return sql;
			}
			String  resSql = sql;

			for(int i = 0; i < targets.length; i++){

				if(null == targets[i]){	//去掉null
					targets[i] = "NULL";
				}
				if(targets[i].contains("'")){	//转换'
					targets[i] = targets[i].replace("'", "''");
				}

				resSql = StringUtils.replaceOnce(resSql, "?", targets[i]);
			}
			return resSql;
	}

	/**
	 * 组装一个预编译的insert sql
	 * @param tableName
	 * @param keys
	 * @return 预编译的insert sql
	 */
	public static String makePrepareInsertSql(String tableName, List<String> keys) {
		if(keys == null || keys.isEmpty()) {
			return "";
		}
		StringBuffer insertSqlBuffer = new StringBuffer("insert into ").append(tableName).append("(");
		StringBuffer valueSqlBuffer = new StringBuffer("values(");
		for(String key: keys) {
			insertSqlBuffer.append(key).append(",");
			valueSqlBuffer.append("?,");
		}
		insertSqlBuffer.deleteCharAt(insertSqlBuffer.length()-1).append(")");
		valueSqlBuffer.deleteCharAt(valueSqlBuffer.length()-1).append(")");
		return insertSqlBuffer.append(" ").append(valueSqlBuffer).toString();
	}
	
	/**
	 * 插入数据到指定表中
	 * @param brs
	 * @param prepareSql
	 * @param keys
	 * @param data
	 */
	public static void insertList2Table(BatchRecordSet brs, String prepareSql, List<String> keys, List<Map<String,Object>> data) {
		if(brs == null || StringUtils.isBlank(prepareSql) || keys == null || data == null) {
			return;
		}
		if(prepareSql.replaceAll("[^\\?]", "").length() != keys.size()) {
			return;
		}
		List<List<Object>> tableData = new ArrayList<List<Object>>();
		List<Object> rowData = null;
		for(Map<String,Object> map: data) {
			rowData = new ArrayList<Object>();
			for(String key: keys) {
				rowData.add(map.get(key));
			}
			tableData.add(rowData);
		}
		brs.executeBatchSql(prepareSql, tableData);
	}

}

三、日志记录

对于接口批量转移,没有任何记录,鉴于流程转移比较敏感,难免有业务人员追溯,因此可将每一次转移的流程进行日志记录。

表结构:

CREATE TABLE [ecology].[dbo].[transfer_log] (
	[requestid] varchar(20) COLLATE Chinese_PRC_CI_AS,
	[from_id] varchar(100) COLLATE Chinese_PRC_CI_AS, -- 离职人
	[to_id] varchar(100) COLLATE Chinese_PRC_CI_AS, -- 转移的人
	[operator] varchar(100) COLLATE Chinese_PRC_CI_AS, -- 操作者,写死interface
	[transfer_time] varchar(100) COLLATE Chinese_PRC_CI_AS, -- 转移时间
	[interface] varchar(500) COLLATE Chinese_PRC_CI_AS, -- 接口名
	[is_finished] varchar(20) COLLATE Chinese_PRC_CI_AS, -- 是否完成,调用 /api/hrm/permissiontoadjust/processData 接口会返回
	[log] varchar(999) COLLATE Chinese_PRC_CI_AS -- 其他日志
);

四、定时任务

该接口可设计成计划任务,每天晚上转移当日的离职人员流程,没有则跳过。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值