This commit is contained in:
2026-05-12 17:40:32 +08:00
parent dc1d2aba8a
commit d37105f03b
17 changed files with 636 additions and 35 deletions
@@ -0,0 +1,35 @@
package com.budwk.app.flow.listenter;
import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessEventListener;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.service.SchoolOaTodoService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
/**
* 流程实例结束事件监听器。
*/
@IocBean
public class ProcessInstanceEndEventListener implements ProcessEventListener {
@Inject
private Dao dao;
@Inject
private SchoolOaTodoService schoolOaTodoService;
@Override
public void onEvent(ProcessEvent event) {
if (event.getEventType() == ProcessEventTypeEnum.PROCESS_INSTANCE_END) {
Long instanceId = event.getSourceId();
ProcessInstance instance = dao.fetch(ProcessInstance.class, instanceId);
// 只在流程正常完成时把学校OA节点名称改为完结,拒绝结束等状态不做完结同步。
if (instance != null && ProcessInstanceStateEnum.FINISHED.getCode().equals(instance.getState())) {
schoolOaTodoService.updateFlowFinishedStepName(instanceId);
}
}
}
}
@@ -4,6 +4,7 @@ import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessEventListener;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import com.budwk.app.flow.service.SchoolOaTodoService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -16,12 +17,15 @@ public class ProcessTaskEndEventListener implements ProcessEventListener {
@Inject
private Dao dao;
@Inject
private SchoolOaTodoService schoolOaTodoService;
@Override
public void onEvent(ProcessEvent event) {
if (event.getEventType() == ProcessEventTypeEnum.PROCESS_TASK_END) {
Long sourceId = event.getSourceId();
ProcessTask task = dao.fetch(ProcessTask.class, sourceId);
schoolOaTodoService.completeTodo(task);
System.out.println("流程任务完成事件:" + task.getDisplayName());
}
}
@@ -9,6 +9,7 @@ import com.budwk.app.flow.engine.event.ProcessEventListener;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import com.budwk.app.flow.service.SchoolOaTodoService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -21,12 +22,15 @@ public class ProcessTaskRevokeEventListener implements ProcessEventListener {
@Inject
private Dao dao;
@Inject
private SchoolOaTodoService schoolOaTodoService;
@Override
public void onEvent(ProcessEvent event) {
if(event.getEventType()== ProcessEventTypeEnum.PROCESS_TASK_REVOKE){
Long taskId = event.getSourceId();
ProcessTask task = dao.fetch(ProcessTask.class, taskId);
schoolOaTodoService.deleteTodo(task);
String taskDisplayName = task.getDisplayName();
@@ -8,6 +8,7 @@ import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.entity.ProcessTaskActor;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import com.budwk.app.web.commons.base.Globals;
import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
@@ -37,6 +38,7 @@ public class ProcessTaskStartEventListener implements ProcessEventListener {
Long sourceId = event.getSourceId();
ProcessTask task = dao.fetch(ProcessTask.class, sourceId);
sendMessage(task);
}
}
@@ -0,0 +1,44 @@
package com.budwk.app.flow.service;
import com.budwk.app.flow.entity.ProcessTask;
/**
* 学校 OA 待办同步服务。
*/
public interface SchoolOaTodoService {
/**
* 根据本系统流程任务创建学校 OA 待办。
*
* @param processTask 本系统已创建的流程任务,需包含任务 ID、流程实例 ID、节点名称和 PC/H5 表单地址
*/
void createTodo(ProcessTask processTask);
/**
* 根据本系统流程任务完成学校 OA 待办。
*
* @param processTask 本系统已完成的流程任务,需包含任务 ID,用于拼接学校 OA 待办唯一值
*/
void completeTodo(ProcessTask processTask);
/**
* 根据本系统流程任务删除学校 OA 单条待办。
*
* @param processTask 本系统待删除待办对应的流程任务,需包含任务 ID,用于拼接学校 OA 待办唯一值
*/
void deleteTodo(ProcessTask processTask);
/**
* 根据本系统流程实例 ID 删除学校 OA 流程。
*
* @param flowId 创建学校 OA 待办时传入的流程实例 ID
*/
void deleteFlow(Long flowId);
/**
* 流程最终完成后修改学校 OA 已办信息。
*
* @param processInstanceId 本系统流程实例 ID,用于定位最后一个已完成审批任务并把学校 OA 节点名称改为完结
*/
void updateFlowFinishedStepName(Long processInstanceId);
}
@@ -29,6 +29,7 @@ import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.service.ProcessDefineService;
import com.budwk.app.flow.service.ProcessInstanceService;
import com.budwk.app.flow.service.ProcessTaskService;
import com.budwk.app.flow.service.SchoolOaTodoService;
import com.budwk.app.flow.vo.HighLightVO;
import com.budwk.app.flow.vo.ProcessInstanceVO;
import com.budwk.app.flow.vo.ProcessTaskVO;
@@ -55,6 +56,8 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
@Inject
private ProcessDefineService processDefineService;
@Inject
private SchoolOaTodoService schoolOaTodoService;
@Override
@Aop(TransAop.READ_COMMITTED)
@@ -556,6 +559,7 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
public void deleteProcessInstanceById(Long processInstanceId) {
List<ProcessTask> taskList = dao().query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId));
List<Long> taskIds = taskList.stream().map(ProcessTask::getId).toList();
schoolOaTodoService.deleteFlow(processInstanceId);
dao().clear(ProcessTask.class, Cnd.where(ProcessTask::getId, "in", taskIds));
dao().clear(ProcessInstance.class, Cnd.where(ProcessInstance::getId, "=", processInstanceId));
}
@@ -29,6 +29,7 @@ import com.budwk.app.flow.enums.*;
import com.budwk.app.flow.service.ProcessDefineService;
import com.budwk.app.flow.service.ProcessInstanceService;
import com.budwk.app.flow.service.ProcessTaskService;
import com.budwk.app.flow.service.SchoolOaTodoService;
import com.budwk.app.flow.vo.ProcessTaskVO;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import org.nutz.aop.interceptor.ioc.TransAop;
@@ -55,6 +56,8 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
@Inject
private ProcessDefineService processDefineService;
@Inject
private SchoolOaTodoService schoolOaTodoService;
@Override
public boolean save(ProcessTask param) {
@@ -198,6 +201,7 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
System.out.println("创建任务:" + processTask.getTaskName() + "," + processTask.getDisplayName());
processTaskList.add(processTask);
addTaskActor(processTask.getId(), getTaskActors(taskModel, execution));
schoolOaTodoService.createTodo(processTask);
return processTaskList;
}
@@ -409,6 +413,7 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
System.out.println("创建会签任务:" + processTask.getTaskName() + "," + processTask.getDisplayName());
processTaskList.add(processTask);
addTaskActor(processTask.getId(), CollectionUtil.newArrayList(taskActor));
schoolOaTodoService.createTodo(processTask);
});
ProcessInstanceService processInstanceService = ServiceContext.find(ProcessInstanceService.class);
// 更新会签变量到流程实例参数中
@@ -0,0 +1,389 @@
package com.budwk.app.flow.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.entity.ProcessCategory;
import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.entity.ProcessTaskActor;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.service.SchoolOaTodoService;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.web.commons.base.Globals;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.Date;
import java.util.List;
/**
* 学校 OA 待办同步服务实现。
*/
@Slf4j
@IocBean
public class SchoolOaTodoServiceImpl implements SchoolOaTodoService {
private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
@Inject
private Dao dao;
@Inject
private PropertiesProxy conf;
@Override
public void createTodo(ProcessTask processTask) {
if (processTask == null || processTask.getId() == null) {
throw new BaseException("学校OA待办同步失败:流程任务为空");
}
if (Long.valueOf(0L).equals(processTask.getTaskParentId()) && "startTask".equals(processTask.getTaskName())) {
return;
}
ProcessInstance instance = dao.fetch(ProcessInstance.class, processTask.getProcessInstanceId());
if (instance == null) {
throw new BaseException("学校OA待办同步失败:流程实例不存在,taskId={}", processTask.getId());
}
ProcessDefine define = dao.fetch(ProcessDefine.class, instance.getProcessDefineId());
if (define == null) {
throw new BaseException("学校OA待办同步失败:流程定义不存在,instanceId={}", instance.getId());
}
Sys_user creator = dao.fetch(Sys_user.class, instance.getOperator());
if (creator == null || StrUtil.isBlank(creator.getLoginname()) || StrUtil.isBlank(creator.getUsername())) {
throw new BaseException("学校OA待办同步失败:流程发起人工号或姓名为空,userId={}", instance.getOperator());
}
List<ProcessTaskActor> actors = dao.query(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTask.getId()));
if (actors.isEmpty()) {
throw new BaseException("学校OA待办同步失败:流程任务接收人为空,taskId={}", processTask.getId());
}
String pcUrl = buildTaskUrl(processTask.getFormKey(), processTask);
String appUrl = buildTaskUrl(processTask.getH5FormKey(), processTask);
String flowTypeName = getFlowTypeName(define);
String title = getInstanceTitle(instance, define);
String creatorTime = formatTime(instance.getCreatedAt());
String receiveTime = formatTime(processTask.getCreatedAt());
for (ProcessTaskActor actor : actors) {
sendCreateTodo(processTask, instance, creator, actor, flowTypeName, title, creatorTime, receiveTime, pcUrl, appUrl);
}
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void completeTodo(ProcessTask processTask) {
if (processTask == null || processTask.getId() == null) {
throw new BaseException("学校OA待办完成失败:流程任务为空");
}
if (Long.valueOf(0L).equals(processTask.getTaskParentId()) && "startTask".equals(processTask.getTaskName())) {
return;
}
List<ProcessTaskActor> actors = dao.query(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTask.getId()));
if (actors.isEmpty()) {
throw new BaseException("学校OA待办完成失败:流程任务接收人为空,taskId={}", processTask.getId());
}
for (ProcessTaskActor actor : actors) {
sendCompleteTodo(processTask, actor);
}
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void deleteTodo(ProcessTask processTask) {
if (processTask == null || processTask.getId() == null) {
throw new BaseException("学校OA待办删除失败:流程任务为空");
}
if (Long.valueOf(0L).equals(processTask.getTaskParentId()) && "startTask".equals(processTask.getTaskName())) {
return;
}
// 仅处理仍处于进行中的任务,避免已完成、已撤回等历史任务重复触发学校OA待办删除。
if (!ProcessTaskStateEnum.DOING.getCode().equals(processTask.getTaskState())) {
return;
}
List<ProcessTaskActor> actors = dao.query(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTask.getId()));
if (actors.isEmpty()) {
throw new BaseException("学校OA待办删除失败:流程任务接收人为空,taskId={}", processTask.getId());
}
for (ProcessTaskActor actor : actors) {
sendDeleteTodo(processTask, actor);
}
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void deleteFlow(Long flowId) {
if (flowId == null) {
throw new BaseException("学校OA流程删除失败:流程实例ID为空");
}
String deleteFlowUrl = requireConfig("school-oa.todo.delete-flow-url").replace("{flowId}", String.valueOf(flowId));
sendTodoRequest(deleteFlowUrl, "学校OA流程删除失败", "学校OA流程删除异常");
log.info("学校OA流程删除成功,flowId={}", flowId);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void updateFlowFinishedStepName(Long processInstanceId) {
if (processInstanceId == null) {
throw new BaseException("学校OA待办修改失败:流程实例ID为空");
}
ProcessInstance instance = dao.fetch(ProcessInstance.class, processInstanceId);
if (instance == null) {
throw new BaseException("学校OA待办修改失败:流程实例不存在,instanceId={}", processInstanceId);
}
ProcessDefine define = dao.fetch(ProcessDefine.class, instance.getProcessDefineId());
if (define == null) {
throw new BaseException("学校OA待办修改失败:流程定义不存在,instanceId={}", instance.getId());
}
Sys_user creator = dao.fetch(Sys_user.class, instance.getOperator());
if (creator == null || StrUtil.isBlank(creator.getLoginname()) || StrUtil.isBlank(creator.getUsername())) {
throw new BaseException("学校OA待办修改失败:流程发起人工号或姓名为空,userId={}", instance.getOperator());
}
List<ProcessTask> finishedTaskList = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode())
.desc(ProcessTask::getId));
for (ProcessTask task : finishedTaskList) {
List<ProcessTaskActor> actors = dao.query(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", task.getId()));
if (!actors.isEmpty()) {
String pcUrl = buildTaskUrl(task.getFormKey(), task);
String appUrl = buildTaskUrl(task.getH5FormKey(), task);
String flowTypeName = getFlowTypeName(define);
String title = getInstanceTitle(instance, define);
String creatorTime = formatTime(instance.getCreatedAt());
String receiveTime = formatTime(task.getCreatedAt());
for (ProcessTaskActor actor : actors) {
sendUpdateFinishedTodo(task, instance, creator, actor, flowTypeName, title, creatorTime, receiveTime, pcUrl, appUrl);
}
return;
}
}
throw new BaseException("学校OA待办修改失败:未找到可修改的已完成流程任务,instanceId={}", processInstanceId);
}
/**
* 调用学校 OA 创建待办接口,接口失败时直接抛出异常,让本系统事务回滚。
*/
private void sendCreateTodo(ProcessTask task, ProcessInstance instance, Sys_user creator, ProcessTaskActor actor,
String flowTypeName, String title, String creatorTime, String receiveTime,
String pcUrl, String appUrl) {
if (StrUtil.isBlank(actor.getActorAccount()) || StrUtil.isBlank(actor.getActorName())) {
throw new BaseException("学校OA待办同步失败:接收人工号或姓名为空,taskId={}actorId={}", task.getId(), actor.getActorId());
}
JSONObject body = new JSONObject();
body.set("flowTypeName", flowTypeName);
body.set("flowId", String.valueOf(instance.getId()));
body.set("title", title);
body.set("stepName", task.getDisplayName());
body.set("creatorCode", creator.getLoginname());
body.set("creatorName", creator.getUsername());
body.set("creatorTime", creatorTime);
body.set("pcUrl", pcUrl);
body.set("appUrl", appUrl);
body.set("pcPreviewUrl", pcUrl);
body.set("appPreviewUrl", appUrl);
body.set("uniqueId", task.getId() + "_" + actor.getActorId());
body.set("receiveCode", actor.getActorAccount());
body.set("receiveName", actor.getActorName());
body.set("receiveTime", receiveTime);
String insertUrl = requireConfig("school-oa.todo.insert-url");
try {
HttpRequest request = HttpUtil.createPost(insertUrl);
setTodoRequestHeaders(request);
request.body(JSONUtil.toJsonStr(body));
String responseBody = request.execute().body();
JSONObject response = JSONUtil.parseObj(responseBody);
if (response.getInt("state", 0) != 200) {
throw new BaseException("学校OA待办创建失败:{}", response.getStr("message", responseBody));
}
log.info("学校OA待办创建成功,taskId={}receiveCode={}response={}", task.getId(), actor.getActorAccount(), responseBody);
} catch (BaseException e) {
throw e;
} catch (Exception e) {
throw new BaseException("学校OA待办创建异常:{}", e.getMessage());
}
}
/**
* 调用学校 OA 完成待办接口。
* 参数使用创建待办时约定的 uniqueId,格式为“流程任务ID_参与人ID”;
* 返回值为学校 OA JSON 结果,state=200 表示已成功把该待办改为已办。
*/
private void sendCompleteTodo(ProcessTask task, ProcessTaskActor actor) {
if (StrUtil.isBlank(actor.getActorId()) || StrUtil.isBlank(actor.getActorAccount())) {
throw new BaseException("学校OA待办完成失败:接收人ID或工号为空,taskId={}actorId={}", task.getId(), actor.getActorId());
}
String uniqueId = task.getId() + "_" + actor.getActorId();
String updateUrl = requireConfig("school-oa.todo.update-url").replace("{uniqueId}", uniqueId);
String responseBody = sendTodoRequest(updateUrl, "学校OA待办完成失败", "学校OA待办完成异常");
log.info("学校OA待办完成成功,taskId={}receiveCode={}uniqueId={}response={}", task.getId(), actor.getActorAccount(), uniqueId, responseBody);
}
/**
* 调用学校 OA 删除单条待办接口。
* 参数使用创建待办时约定的 uniqueId,格式为“流程任务ID_参与人ID”;
* 返回值为学校 OA JSON 结果,state=200 表示已成功删除该待办数据。
*/
private void sendDeleteTodo(ProcessTask task, ProcessTaskActor actor) {
if (StrUtil.isBlank(actor.getActorId()) || StrUtil.isBlank(actor.getActorAccount())) {
throw new BaseException("学校OA待办删除失败:接收人ID或工号为空,taskId={}actorId={}", task.getId(), actor.getActorId());
}
String uniqueId = task.getId() + "_" + actor.getActorId();
String deleteUrl = requireConfig("school-oa.todo.delete-url").replace("{uniqueId}", uniqueId);
String responseBody = sendTodoRequest(deleteUrl, "学校OA待办删除失败", "学校OA待办删除异常");
log.info("学校OA待办删除成功,taskId={}receiveCode={}uniqueId={}response={}", task.getId(), actor.getActorAccount(), uniqueId, responseBody);
}
/**
* 调用学校 OA 修改待办信息接口。
* 参数为学校 OA 要求的完整流程信息,uniqueId 使用“流程任务ID_参与人ID”;
* 流程最终完成后将 stepName 固定改为“完结”,isRemark 固定为 2 表示已办。
*/
private void sendUpdateFinishedTodo(ProcessTask task, ProcessInstance instance, Sys_user creator, ProcessTaskActor actor,
String flowTypeName, String title, String creatorTime, String receiveTime,
String pcUrl, String appUrl) {
if (StrUtil.isBlank(actor.getActorId()) || StrUtil.isBlank(actor.getActorAccount()) || StrUtil.isBlank(actor.getActorName())) {
throw new BaseException("学校OA待办修改失败:接收人ID、工号或姓名为空,taskId={}actorId={}", task.getId(), actor.getActorId());
}
String uniqueId = task.getId() + "_" + actor.getActorId();
JSONObject body = new JSONObject();
body.set("flowTypeName", flowTypeName);
body.set("flowId", String.valueOf(instance.getId()));
body.set("title", title);
body.set("stepName", "完结");
body.set("creatorCode", creator.getLoginname());
body.set("creatorName", creator.getUsername());
body.set("creatorTime", creatorTime);
body.set("pcUrl", pcUrl);
body.set("appUrl", appUrl);
body.set("uniqueId", uniqueId);
body.set("receiveCode", actor.getActorAccount());
body.set("receiveName", actor.getActorName());
body.set("receiveTime", receiveTime);
body.set("isRemark", "2");
String updateHandleUrl = requireConfig("school-oa.todo.update-handle-url").replace("{uniqueId}", uniqueId);
String responseBody = sendTodoRequest(updateHandleUrl, body, "学校OA待办修改失败", "学校OA待办修改异常");
log.info("学校OA待办修改成功,taskId={}receiveCode={}uniqueId={}response={}", task.getId(), actor.getActorAccount(), uniqueId, responseBody);
}
private String getFlowTypeName(ProcessDefine define) {
if (StrUtil.isNotBlank(define.getCategory())) {
ProcessCategory category = dao.fetch(ProcessCategory.class, define.getCategory());
if (category != null && StrUtil.isNotBlank(category.getName())) {
return category.getName();
}
}
if (StrUtil.isNotBlank(define.getDisplayName())) {
return define.getDisplayName();
}
throw new BaseException("学校OA待办同步失败:流程类型名称为空,processDefineId={}", define.getId());
}
private String getInstanceTitle(ProcessInstance instance, ProcessDefine define) {
if (JSONUtil.isTypeJSON(instance.getVariable())) {
String instanceName = JSONUtil.parseObj(instance.getVariable()).getStr(FlowConst.PROCESS_INSTANCE_NAME);
if (StrUtil.isNotBlank(instanceName)) {
return instanceName;
}
}
if (StrUtil.isNotBlank(define.getDisplayName())) {
return define.getDisplayName();
}
throw new BaseException("学校OA待办同步失败:流程标题为空,instanceId={}", instance.getId());
}
private String buildTaskUrl(String formKey, ProcessTask task) {
if (StrUtil.isBlank(formKey)) {
throw new BaseException("学校OA待办同步失败:任务审核地址为空,taskId={}", task.getId());
}
String baseUrl = StrUtil.removeSuffix(Globals.AppDomain, "/") + "/" + StrUtil.removePrefix(formKey, "/");
String separator = baseUrl.contains("?") ? "&" : "?";
return baseUrl + separator + "taskId=" + task.getId() + "&instanceId=" + task.getProcessInstanceId();
}
private String formatTime(Long timestamp) {
if (timestamp == null) {
throw new BaseException("学校OA待办同步失败:待办时间为空");
}
return DateUtil.format(new Date(timestamp), DATE_PATTERN);
}
private String requireConfig(String key) {
String value = conf.get(key);
if (StrUtil.isBlank(value)) {
throw new BaseException("学校OA待办同步失败:配置{}为空", key);
}
return value;
}
/**
* 统一设置学校 OA 待办接口请求头。
* 请求头参数包含 app-code、secret-key、domain-name 和可选 intercept-domain-name
* 学校 OA 返回 JSON 字符串,由各调用方法按业务接口分别解析 state、message、data。
*/
private void setTodoRequestHeaders(HttpRequest request) {
request.header("Content-Type", "application/json");
request.header("app-code", requireConfig("school-oa.todo.app-code"));
request.header("secret-key", requireConfig("school-oa.todo.secret-key"));
request.header("domain-name", requireConfig("school-oa.todo.domain-name"));
String interceptDomainName = conf.get("school-oa.todo.intercept-domain-name", "");
if (StrUtil.isNotBlank(interceptDomainName)) {
request.header("intercept-domain-name", interceptDomainName);
}
}
private String sendTodoRequest(String url, String failMessage, String errorMessage) {
return sendTodoRequest(url, null, failMessage, errorMessage);
}
private String sendTodoRequest(String url, JSONObject body, String failMessage, String errorMessage) {
try {
HttpRequest request = HttpUtil.createPost(url);
setTodoRequestHeaders(request);
if (body != null) {
request.body(JSONUtil.toJsonStr(body));
}
String responseBody = request.execute().body();
JSONObject response = JSONUtil.parseObj(responseBody);
if (response.getInt("state", 0) != 200) {
throw new BaseException("{}{}", failMessage, response.getStr("message", responseBody));
}
return responseBody;
} catch (BaseException e) {
throw e;
} catch (Exception e) {
throw new BaseException("{}{}", errorMessage, e.getMessage());
}
}
}
+17 -5
View File
@@ -1,11 +1,11 @@
nutz:
application:
name: zhgh_whcp_new
name: zhgh_ypi
mvc:
ignore: ^(.+[.])(jsp|png|gif|jpg|js|css|jspx|jpeg|html|mp3|mp4|ico|svg|vue)$
exclusions: /favicon/*,/assets/*,/druid/*,/upload/*,/components/*
server:
port: 8080
port: 80
host: 0.0.0.0
jetty:
@@ -21,7 +21,7 @@ jetty:
#结合ftp使用,或用nginx代理ftp路径
#staticPath: /Users/wizzer/temp/files
#开发模式静态资源
staticPathLocal: D:/myProject/zhgh_whcp_new/src/main/resources/static
staticPathLocal: E:/projects/zhgh_ypi/src/main/resources/static
security:
tokenName: saToken
@@ -70,7 +70,7 @@ redis:
#nodes=192.168.6.31:6377,192.168.6.31:6378,192.168.6.28:6377,192.168.6.28:6378,192.168.6.34:6377,192.168.6.34:6378
jdbc:
url: jdbc:mysql://192.168.21.203/zhgh_whcp_new?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
url: jdbc:mysql://192.168.21.203/zhgh_ypi?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: root
validationQuery: select 1
@@ -86,7 +86,7 @@ xsssql:
beetl:
RESOURCE:
#本地路径,开发时设置本地路径,便于调试
rootLocal: D:/myProject/zhgh_whcp_new/src/main/resources/views/
rootLocal: E:/projects/zhgh_ypi/src/main/resources/views/
root: views/
DELIMITER_STATEMENT_START: "<!--#"
DELIMITER_STATEMENT_END: "#-->"
@@ -164,3 +164,15 @@ minio:
endPoint: http://192.168.21.214:9000
bucket: jshvc
school-oa:
todo:
insert-url: https://i.ypi.edu.cn/string_unified_handle_service/handle/insert
update-url: https://i.ypi.edu.cn/string_unified_handle_service/handle/{uniqueId}/updateUniqueId
update-handle-url: https://i.ypi.edu.cn/string_unified_handle_service/handle/{uniqueId}/updateHandle
delete-url: https://i.ypi.edu.cn/string_unified_handle_service/handle/{uniqueId}/deleteUniqueId
delete-flow-url: https://i.ypi.edu.cn/string_unified_handle_service/handle/{flowId}/deleteFlowId
app-code: 1553e2f0c1c54b5d854ffee1eae181eb
secret-key: f8a1da28b66644238659bce33f7ead6e
domain-name: i.ypi.edu.cn
intercept-domain-name:
@@ -240,9 +240,15 @@ layout("/layouts/platform.html"){
</el-descriptions>
</el-form>
<el-row type="flex" justify="end" class="mt20">
<el-button type="primary" plain @click="onSave">保存</el-button>
<el-button type="primary" @click="onSubmit" v-if="!taskId">提交</el-button>
<el-button type="primary" @click="onFinishTask" v-else>提交</el-button>
<el-button type="primary" plain @click="onSave" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "保存" }}
</el-button>
<el-button type="primary" @click="onSubmit" :loading="submitLoading" v-if="!taskId">
{{ submitLoading ? "提交中..." : "提交" }}
</el-button>
<el-button type="primary" @click="onFinishTask" :loading="submitLoading" v-else>
{{ submitLoading ? "提交中..." : "提交" }}
</el-button>
</el-row>
</el-card>
</div>
@@ -256,6 +262,9 @@ layout("/layouts/platform.html"){
bizId: GetQueryString("bizId"),
taskId: GetQueryString("taskId"),
formData: {},
// 保存和提交请求期间控制按钮转圈,避免用户重复点击。
saveLoading: false,
submitLoading: false,
formRules: {
helpUserId: [{required: true, message: "必填", trigger: ["change", "blur"]}],
payUserId: [{required: true, message: "必填", trigger: ["change", "blur"]}],
@@ -363,11 +372,14 @@ layout("/layouts/platform.html"){
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.submitLoading = true
this.$axios.post("/platform/condolence/apply/save", {data: JSON.stringify(this.formData)}).then(res => {
if (res.code === 0) {
this.$message.success(res.msg)
commonUtil.pjaxPush("/platform/condolence/mine")
}
}).finally(() => {
this.submitLoading = false
})
})
},
@@ -379,6 +391,7 @@ layout("/layouts/platform.html"){
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.submitLoading = true
this.$axios.post("/platform/condolence/apply/submit", {
data: JSON.stringify(this.formData)
}).then(res => {
@@ -386,6 +399,8 @@ layout("/layouts/platform.html"){
this.$message.success(res.msg)
commonUtil.pjaxPush("/platform/condolence/mine")
}
}).finally(() => {
this.submitLoading = false
})
})
}
@@ -399,6 +414,7 @@ layout("/layouts/platform.html"){
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.submitLoading = true
this.$axios.post("/platform/condolence/apply/submitAgain", {
data: JSON.stringify(this.formData),
taskId: GetQueryString("taskId")
@@ -407,6 +423,8 @@ layout("/layouts/platform.html"){
this.$message.success(res.msg)
commonUtil.pjaxPush("/platform/condolence/mine")
}
}).finally(() => {
this.submitLoading = false
})
})
}
@@ -88,9 +88,15 @@ layout("/layouts/platform.html"){
</el-form>
<el-row type="flex" justify="end">
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "退回到发起人" }}
</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "拒绝申请" }}
</el-button>
<el-button @click="handleTaskAction(1)" size="small" type="primary" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "同意申请" }}
</el-button>
</el-row>
</div>
</condolence-info>
@@ -124,7 +130,9 @@ layout("/layouts/platform.html"){
{prop: 'instanceState', label: '流程状态'},
],
formData: {},
showApprovalForm: false
showApprovalForm: false,
// 审核提交请求期间控制按钮转圈,避免重复提交审批意见。
submitLoading: false
}
},
methods: {
@@ -150,6 +158,7 @@ layout("/layouts/platform.html"){
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.submitLoading = true
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
@@ -161,6 +170,8 @@ layout("/layouts/platform.html"){
this.$message.success(res.msg)
this.doSearch()
}
}).finally(() => {
this.submitLoading = false
})
})
},
@@ -108,9 +108,15 @@ layout("/layouts/platform.html"){
</el-form>
<el-row type="flex" justify="end">
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "退回到发起人" }}
</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "拒绝申请" }}
</el-button>
<el-button @click="handleTaskAction(1)" size="small" type="primary" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "同意申请" }}
</el-button>
</el-row>
</div>
</condolence-info>
@@ -147,7 +153,9 @@ layout("/layouts/platform.html"){
unionList: [],
unitList: [],
formData: {},
showApprovalForm: false
showApprovalForm: false,
// 审核提交请求期间控制按钮转圈,避免重复提交审批意见。
submitLoading: false
}
},
methods: {
@@ -173,6 +181,7 @@ layout("/layouts/platform.html"){
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.submitLoading = true
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
@@ -184,6 +193,8 @@ layout("/layouts/platform.html"){
this.$message.success(res.msg)
this.doSearch()
}
}).finally(() => {
this.submitLoading = false
})
})
},
@@ -108,9 +108,15 @@ layout("/layouts/platform.html"){
</el-form>
<el-row type="flex" justify="end">
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "退回到发起人" }}
</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "拒绝申请" }}
</el-button>
<el-button @click="handleTaskAction(1)" size="small" type="primary" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "同意申请" }}
</el-button>
</el-row>
</div>
</condolence-info>
@@ -147,7 +153,9 @@ layout("/layouts/platform.html"){
unionList: [],
unitList: [],
formData: {},
showApprovalForm: false
showApprovalForm: false,
// 审核提交请求期间控制按钮转圈,避免重复提交审批意见。
submitLoading: false
}
},
methods: {
@@ -173,6 +181,7 @@ layout("/layouts/platform.html"){
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.submitLoading = true
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
@@ -184,6 +193,8 @@ layout("/layouts/platform.html"){
this.$message.success(res.msg)
this.doSearch()
}
}).finally(() => {
this.submitLoading = false
})
})
},
@@ -377,9 +377,15 @@ layout("/layouts/platform_h5.html"){
<!-- 提交按钮 -->
<div class="form-actions">
<van-button native-type="button" @click="onSave" round type="info" plain>保存申请</van-button>
<van-button @click="onSubmit" round type="info" v-if="!taskId">提交申请</van-button>
<van-button @click="onFinishTask" round type="info" v-else>提交申请</van-button>
<van-button native-type="button" @click="onSave" round type="info" plain :loading="saveLoading">
{{ saveLoading ? "保存中..." : "保存申请" }}
</van-button>
<van-button @click="onSubmit" round type="info" :loading="submitLoading" v-if="!taskId">
{{ submitLoading ? "提交中..." : "提交申请" }}
</van-button>
<van-button @click="onFinishTask" round type="info" :loading="submitLoading" v-else>
{{ submitLoading ? "提交中..." : "提交申请" }}
</van-button>
</div>
</van-form>
</div>
@@ -394,6 +400,9 @@ layout("/layouts/platform_h5.html"){
bizId: GetQueryString("bizId"),
taskId: GetQueryString("taskId"),
formData: {},
// 保存和提交请求期间控制按钮转圈,避免用户重复点击。
saveLoading: false,
submitLoading: false,
chooseType: {},
userOptions: [],
@@ -540,11 +549,14 @@ layout("/layouts/platform_h5.html"){
title: "提示",
message: "您确定保存吗?"
}).then(() => {
this.saveLoading = true
this.$axios.post("/platform/condolence/apply/save", { data: JSON.stringify(this.formData) }).then(res => {
if (res.code === 0) {
this.$toast(res.msg)
this.$pjaxReplace("/platform/condolence/mine/h5")
}
}).finally(() => {
this.saveLoading = false
})
})
},
@@ -558,11 +570,14 @@ layout("/layouts/platform_h5.html"){
title: "提示",
message: "您确定要提交申请吗?"
}).then(() => {
this.submitLoading = true
this.$axios.post("/platform/condolence/apply/submit", { data: JSON.stringify(this.formData) }).then(res => {
if (res.code === 0) {
this.$toast(res.msg)
this.$pjaxReplace("/platform/condolence/mine/h5")
}
}).finally(() => {
this.submitLoading = false
})
})
})
@@ -577,6 +592,7 @@ layout("/layouts/platform_h5.html"){
title: "提示",
message: "您确定要提交申请吗?"
}).then(() => {
this.submitLoading = true
this.$axios.post("/platform/condolence/apply/submitAgain", {
data: JSON.stringify(this.formData),
taskId: GetQueryString("taskId")
@@ -585,6 +601,8 @@ layout("/layouts/platform_h5.html"){
this.$toast(res.msg)
this.$pjaxReplace("/platform/condolence/mine/h5")
}
}).finally(() => {
this.submitLoading = false
})
})
})
@@ -79,9 +79,15 @@ layout("/layouts/platform_h5.html"){
</van-cell-group>
</van-form>
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
<van-button type="danger" block @click="handleTaskAction(6)">退回</van-button>
<van-button type="danger" block @click="handleTaskAction(20)">不同意</van-button>
<van-button type="primary" block @click="handleTaskAction(1)">同意</van-button>
<van-button type="danger" block @click="handleTaskAction(6)" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "退回" }}
</van-button>
<van-button type="danger" block @click="handleTaskAction(20)" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "不同意" }}
</van-button>
<van-button type="primary" block @click="handleTaskAction(1)" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "同意" }}
</van-button>
</div>
</div>
</info>
@@ -109,7 +115,9 @@ layout("/layouts/platform_h5.html"){
},
typeOptions: [],
formData: {},
showApprovalForm: false
showApprovalForm: false,
// 审核提交请求期间控制按钮转圈,避免重复提交审批意见。
submitLoading: false
}
},
methods: {
@@ -145,6 +153,7 @@ layout("/layouts/platform_h5.html"){
title: "提示",
message: "您确定要提交吗?"
}).then(() => {
this.submitLoading = true
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
@@ -156,6 +165,8 @@ layout("/layouts/platform_h5.html"){
this.$toast.success(res.msg)
this.doSearch()
}
}).finally(() => {
this.submitLoading = false
})
}).catch(() => {
@@ -79,9 +79,15 @@ layout("/layouts/platform_h5.html"){
</van-cell-group>
</van-form>
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
<van-button type="danger" block @click="handleTaskAction(6)">退回</van-button>
<van-button type="danger" block @click="handleTaskAction(20)">不同意</van-button>
<van-button type="primary" block @click="handleTaskAction(1)">同意</van-button>
<van-button type="danger" block @click="handleTaskAction(6)" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "退回" }}
</van-button>
<van-button type="danger" block @click="handleTaskAction(20)" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "不同意" }}
</van-button>
<van-button type="primary" block @click="handleTaskAction(1)" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "同意" }}
</van-button>
</div>
</div>
</info>
@@ -109,7 +115,9 @@ layout("/layouts/platform_h5.html"){
},
typeOptions: [],
formData: {},
showApprovalForm: false
showApprovalForm: false,
// 审核提交请求期间控制按钮转圈,避免重复提交审批意见。
submitLoading: false
}
},
methods: {
@@ -145,6 +153,7 @@ layout("/layouts/platform_h5.html"){
title: "提示",
message: "您确定要提交吗?"
}).then(() => {
this.submitLoading = true
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
@@ -156,6 +165,8 @@ layout("/layouts/platform_h5.html"){
this.$toast.success(res.msg)
this.doSearch()
}
}).finally(() => {
this.submitLoading = false
})
}).catch(() => {
@@ -79,9 +79,15 @@ layout("/layouts/platform_h5.html"){
</van-cell-group>
</van-form>
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
<van-button type="danger" block @click="handleTaskAction(6)">退回</van-button>
<van-button type="danger" block @click="handleTaskAction(20)">不同意</van-button>
<van-button type="primary" block @click="handleTaskAction(1)">同意</van-button>
<van-button type="danger" block @click="handleTaskAction(6)" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "退回" }}
</van-button>
<van-button type="danger" block @click="handleTaskAction(20)" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "不同意" }}
</van-button>
<van-button type="primary" block @click="handleTaskAction(1)" :loading="submitLoading">
{{ submitLoading ? "提交中..." : "同意" }}
</van-button>
</div>
</div>
</info>
@@ -109,7 +115,9 @@ layout("/layouts/platform_h5.html"){
},
typeOptions: [],
formData: {},
showApprovalForm: false
showApprovalForm: false,
// 审核提交请求期间控制按钮转圈,避免重复提交审批意见。
submitLoading: false
}
},
methods: {
@@ -145,6 +153,7 @@ layout("/layouts/platform_h5.html"){
title: "提示",
message: "您确定要提交吗?"
}).then(() => {
this.submitLoading = true
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
@@ -156,6 +165,8 @@ layout("/layouts/platform_h5.html"){
this.$toast.success(res.msg)
this.doSearch()
}
}).finally(() => {
this.submitLoading = false
})
}).catch(() => {