【同步】BOOT 和 CLOUD 的功能

pull/203/MERGE
YunaiV 2025-08-16 19:02:44 +08:00
parent 2382c3d844
commit 110c38bf6e
49 changed files with 574 additions and 262 deletions

View File

@ -1259,14 +1259,16 @@ CREATE TABLE `system_mail_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`user_id` bigint NULL DEFAULT NULL COMMENT '用户编号', `user_id` bigint NULL DEFAULT NULL COMMENT '用户编号',
`user_type` tinyint NULL DEFAULT NULL COMMENT '用户类型', `user_type` tinyint NULL DEFAULT NULL COMMENT '用户类型',
`to_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收邮箱地址', `to_mails` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收邮箱地址',
`cc_mails` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '抄送邮箱地址',
`bcc_mails` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密送邮箱地址',
`account_id` bigint NOT NULL COMMENT '邮箱账号编号', `account_id` bigint NOT NULL COMMENT '邮箱账号编号',
`from_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发送邮箱地址', `from_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发送邮箱地址',
`template_id` bigint NOT NULL COMMENT '模板编号', `template_id` bigint NOT NULL COMMENT '模板编号',
`template_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板编码', `template_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板编码',
`template_nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版发送人名称', `template_nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版发送人名称',
`template_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件标题', `template_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件标题',
`template_content` varchar(10240) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件内容', `template_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件内容',
`template_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件参数', `template_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件参数',
`send_status` tinyint NOT NULL DEFAULT 0 COMMENT '发送状态', `send_status` tinyint NOT NULL DEFAULT 0 COMMENT '发送状态',
`send_time` datetime NULL DEFAULT NULL COMMENT '发送时间', `send_time` datetime NULL DEFAULT NULL COMMENT '发送时间',

View File

@ -60,6 +60,10 @@ public interface ErrorCodeConstants {
ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在"); ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在");
ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!"); ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!");
ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!"); ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!");
ErrorCode TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING = new ErrorCode(1_009_005_017, "撤回失败,流程实例未运行!");
ErrorCode TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS = new ErrorCode(1_009_005_018, "撤回失败,未查询到用户已办任务!");
ErrorCode TASK_WITHDRAW_FAIL_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,此流程不允许撤回操作!");
ErrorCode TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,下一节点不满足撤回条件!");
// ========== 动态表单模块 1-009-010-000 ========== // ========== 动态表单模块 1-009-010-000 ==========
ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在"); ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在");

View File

@ -35,6 +35,7 @@ public enum BpmReasonEnum {
APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"), APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"),
APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"), APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"),
CANCEL_BY_PROCESS_CLEAN("进程清理自动取消"), CANCEL_BY_PROCESS_CLEAN("进程清理自动取消"),
CANCEL_BY_WITHDRAW("前一任务撤回,系统自动取消"),
; ;
private final String reason; private final String reason;

View File

@ -72,6 +72,9 @@ public class BpmModelMetaInfoVO {
@Schema(description = "允许撤销审批中的申请", example = "true") @Schema(description = "允许撤销审批中的申请", example = "true")
private Boolean allowCancelRunningProcess; private Boolean allowCancelRunningProcess;
@Schema(description = "允许允许审批人撤回任务", example = "false")
private Boolean allowWithdrawTask;
@Schema(description = "流程 ID 规则", example = "{}") @Schema(description = "流程 ID 规则", example = "{}")
private ProcessIdRule processIdRule; private ProcessIdRule processIdRule;

View File

@ -219,6 +219,14 @@ public class BpmTaskController {
return success(true); return success(true);
} }
@PutMapping("/withdraw")
@Operation(summary = "撤回任务")
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
public CommonResult<Boolean> withdrawTask(@RequestParam("taskId") String taskId) {
taskService.withdrawTask(getLoginUserId(), taskId);
return success(true);
}
@GetMapping("/list-by-parent-task-id") @GetMapping("/list-by-parent-task-id")
@Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表 @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表
@Parameter(name = "parentTaskId", description = "父级任务编号", required = true) @Parameter(name = "parentTaskId", description = "父级任务编号", required = true)

View File

@ -172,6 +172,11 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
*/ */
private Boolean allowCancelRunningProcess; private Boolean allowCancelRunningProcess;
/**
*
*/
private Boolean allowWithdrawTask;
/** /**
* ID * ID
*/ */

View File

@ -797,9 +797,9 @@ public class BpmnModelUtils {
// 情况StartEvent/EndEvent/UserTask/ServiceTask // 情况StartEvent/EndEvent/UserTask/ServiceTask
if (currentElement instanceof StartEvent if (currentElement instanceof StartEvent
|| currentElement instanceof EndEvent || currentElement instanceof EndEvent
|| currentElement instanceof UserTask || currentElement instanceof UserTask
|| currentElement instanceof ServiceTask) { || currentElement instanceof ServiceTask) {
// 添加节点 // 添加节点
FlowNode flowNode = (FlowNode) currentElement; FlowNode flowNode = (FlowNode) currentElement;
resultElements.add(flowNode); resultElements.add(flowNode);
@ -908,6 +908,49 @@ public class BpmnModelUtils {
return nextFlowNodes; return nextFlowNodes;
} }
/**
*
*
* @param source
* @return
*/
public static List<UserTask> getNextUserTasks(FlowElement source) {
return getNextUserTasks(source, null, null);
}
/**
*
* @param source
* @param hasSequenceFlow 线 ID线
* @param userTaskList
* @return
*/
public static List<UserTask> getNextUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = Optional.ofNullable(hasSequenceFlow).orElse(new HashSet<>());
userTaskList = Optional.ofNullable(userTaskList).orElse(new ArrayList<>());
// 获取出口连线
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (!sequenceFlows.isEmpty()) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
if (targetFlowElement instanceof UserTask) {
// 若节点为用户任务,加入到结果列表中
userTaskList.add((UserTask) targetFlowElement);
} else {
// 若节点非用户任务,继续递归查找下一个节点
getNextUserTasks(targetFlowElement, hasSequenceFlow, userTaskList);
}
}
}
return userTaskList;
}
/** /**
* *
* *
@ -938,8 +981,8 @@ public class BpmnModelUtils {
*/ */
private static SequenceFlow findMatchSequenceFlowByExclusiveGateway(Gateway gateway, Map<String, Object> variables) { private static SequenceFlow findMatchSequenceFlowByExclusiveGateway(Gateway gateway, Map<String, Object> variables) {
SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& (evalConditionExpress(variables, flow.getConditionExpression()))); && (evalConditionExpress(variables, flow.getConditionExpression())));
if (matchSequenceFlow == null) { if (matchSequenceFlow == null) {
matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId())); flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));

View File

@ -67,7 +67,6 @@ import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
@ -221,11 +220,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
processDefinitionInfo, processDefinitionInfo,
processVariables, activities); processVariables, activities);
// 3.3 如果是发起动作activityId 为开始节点,不校验审批人自选节点
if (ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) {
simulateActivityNodes.removeIf(node ->
BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy().equals(node.getCandidateStrategy()));
}
// 4. 拼接最终数据 // 4. 拼接最终数据
return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
@ -415,7 +409,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
endActivities.forEach(activity -> { endActivities.forEach(activity -> {
// StartEvent只处理 BPMN 的场景。因为SIMPLE 情况下,已经有 START_USER_NODE 节点 // StartEvent只处理 BPMN 的场景。因为SIMPLE 情况下,已经有 START_USER_NODE 节点
if (ELEMENT_EVENT_START.equals(activity.getActivityType()) if (ELEMENT_EVENT_START.equals(activity.getActivityType())
&& BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) { && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())
&& !CollUtil.contains(activities, // 特殊:如果已经存在用户手动创建的 START_USER_NODE_ID 节点,则忽略 StartEvent
historicActivity -> historicActivity.getActivityId().equals(START_USER_NODE_ID))) {
ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID) ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID)
.setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus()); .setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus());
ActivityNode startNode = new ActivityNode().setId(startTask.getId()) ActivityNode startNode = new ActivityNode().setId(startTask.getId())
@ -555,7 +551,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 情况一BPMN 设计器 // 情况一BPMN 设计器
if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) {
List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables); List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables);
return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel, return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(
startUserId, bpmnModel, flowElements,
processDefinitionInfo, processVariables, flowElement, runActivityIds)); processDefinitionInfo, processVariables, flowElement, runActivityIds));
} }
// 情况二SIMPLE 设计器 // 情况二SIMPLE 设计器
@ -563,7 +560,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(),
BpmSimpleModelNodeVO.class); BpmSimpleModelNodeVO.class);
List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables); List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables);
return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel, return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(
startUserId, bpmnModel,
processDefinitionInfo, processVariables, simpleNode, runActivityIds)); processDefinitionInfo, processVariables, simpleNode, runActivityIds));
} }
throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType()); throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType());
@ -618,8 +616,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
return null; return null;
} }
private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, List<FlowElement> flowElements,
BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, BpmProcessDefinitionInfoDO processDefinitionInfo,
Map<String, Object> processVariables,
FlowElement node, Set<String> runActivityIds) { FlowElement node, Set<String> runActivityIds) {
if (runActivityIds.contains(node.getId())) { if (runActivityIds.contains(node.getId())) {
return null; return null;
@ -634,6 +633,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 1. 开始节点 // 1. 开始节点
if (node instanceof StartEvent) { if (node instanceof StartEvent) {
if (CollUtil.contains(flowElements, // 特殊:如果已经存在用户手动创建的 START_USER_NODE_ID 节点,则忽略 StartEvent
flowElement -> flowElement.getId().equals(START_USER_NODE_ID))) {
return null;
}
return activityNode.setName(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getName()) return activityNode.setName(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getName())
.setNodeType(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()); .setNodeType(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType());
} }

View File

@ -250,6 +250,14 @@ public interface BpmTaskService {
*/ */
void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO); void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO);
/**
*
*
* @param userId
* @param taskId
*/
void withdrawTask(Long userId, String taskId);
// ========== Event 事件相关方法 ========== // ========== Event 事件相关方法 ==========
/** /**

View File

@ -196,7 +196,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
/** /**
* processInstanceId * processInstanceId
* *
* @param userId * @param userId
* @param processInstanceId * @param processInstanceId
* @return * @return
*/ */
@ -599,15 +599,15 @@ public class BpmTaskServiceImpl implements BpmTaskService {
/** /**
* *
* * <p>
* 1. * 1.
* 2. * 2.
* *
* @param taskDefinitionKey * @param taskDefinitionKey
* @param variables * @param variables
* @param bpmnModel * @param bpmnModel
* @param nextAssignees * @param nextAssignees
* @param processInstance * @param processInstance
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel, private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
@ -659,7 +659,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
approveUserSelectAssignees = new HashMap<>(); approveUserSelectAssignees = new HashMap<>();
} }
approveUserSelectAssignees.put(nextFlowNode.getId(), assignees); approveUserSelectAssignees.put(nextFlowNode.getId(), assignees);
Map<String,List<Long>> existingApproveUserSelectAssignees = (Map<String,List<Long>>) variables.get( Map<String, List<Long>> existingApproveUserSelectAssignees = (Map<String, List<Long>>) variables.get(
BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES); BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES);
if (CollUtil.isNotEmpty(existingApproveUserSelectAssignees)) { if (CollUtil.isNotEmpty(existingApproveUserSelectAssignees)) {
approveUserSelectAssignees.putAll(existingApproveUserSelectAssignees); approveUserSelectAssignees.putAll(existingApproveUserSelectAssignees);
@ -1177,6 +1177,63 @@ public class BpmTaskServiceImpl implements BpmTaskService {
processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId()); processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId());
} }
@Override
@Transactional(rollbackFor = Exception.class)
public void withdrawTask(Long userId, String taskId) {
// 1.1 查询本人已办任务
HistoricTaskInstance taskInstance = historyService.createHistoricTaskInstanceQuery()
.taskId(taskId).taskAssignee(userId.toString()).finished().singleResult();
if (ObjUtil.isNull(taskInstance)) {
throw exception(TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS);
}
// 1.2 校验流程是否结束
ProcessInstance processInstance = processInstanceService.getProcessInstance(taskInstance.getProcessInstanceId());
if (ObjUtil.isNull(processInstance)) {
throw exception(TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING);
}
// 1.3 判断此流程是否允许撤回
BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.getProcessDefinitionInfo(
processInstance.getProcessDefinitionId());
if (ObjUtil.isNull(processDefinitionInfo) || !Boolean.TRUE.equals(processDefinitionInfo.getAllowWithdrawTask())) {
throw exception(TASK_WITHDRAW_FAIL_NOT_ALLOW);
}
// 1.4 判断下一个节点是否被审批过,如果是则无法撤回
BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskInstance.getProcessDefinitionId());
UserTask userTask = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, taskInstance.getTaskDefinitionKey());
List<String> nextUserTaskKeys = convertList(BpmnModelUtils.getNextUserTasks(userTask), UserTask::getId);
if (CollUtil.isEmpty(nextUserTaskKeys)) {
throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW);
}
// TODO @芋艿是否选择升级flowable版本解决taskCreatedAfter、taskCreatedBefore问题升级7.1.0可以;包括 todo 和 done 那边的查询哇??? 是的!
long nextUserTaskFinishedCount = historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstance.getProcessInstanceId()).taskDefinitionKeys(nextUserTaskKeys)
.taskCreatedAfter(taskInstance.getEndTime()).finished().count();
if (nextUserTaskFinishedCount > 0) {
throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW);
}
// 1.5 获取需要撤回的运行任务
List<Task> runningTasks = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId())
.taskDefinitionKeys(nextUserTaskKeys).active().list();
if (CollUtil.isEmpty(runningTasks)) {
throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW);
}
// 2.1 取消当前任务
List<String> withdrawExecutionIds = new ArrayList<>();
for (Task task : runningTasks) {
// 标记撤回任务为取消
taskService.addComment(task.getId(), taskInstance.getProcessInstanceId(), BpmCommentTypeEnum.CANCEL.getType(),
BpmCommentTypeEnum.CANCEL.formatComment("前一节点撤回"));
updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_WITHDRAW.getReason());
withdrawExecutionIds.add(task.getExecutionId());
}
// 2.2 执行撤回操作
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(processInstance.getProcessInstanceId())
.moveExecutionsToSingleActivityId(withdrawExecutionIds, taskInstance.getTaskDefinitionKey())
.changeState();
}
/** /**
* *
* *
@ -1223,7 +1280,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
} }
// 2. 任务前置通知 // 2. 任务前置通知
if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){ if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())) {
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting(); BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting();
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
@ -1350,7 +1407,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
.taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus()) .taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus())
.finished(); .finished();
if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType()) if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType())
&& sameAssigneeQuery.count() > 0) { && sameAssigneeQuery.count() > 0) {
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
.setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName())); .setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName()));
return; return;
@ -1362,7 +1419,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
return; return;
} }
List<String> sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingFlows( // 获取所有上一个节点 List<String> sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingFlows( // 获取所有上一个节点
BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())), BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())),
SequenceFlow::getSourceRef); SequenceFlow::getSourceRef);
if (sameAssigneeQuery.taskDefinitionKeys(sourceTaskIds).count() > 0) { if (sameAssigneeQuery.taskDefinitionKeys(sourceTaskIds).count() > 0) {
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
@ -1387,7 +1444,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class)); PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
if (userTaskElement.getId().equals(START_USER_NODE_ID) if (userTaskElement.getId().equals(START_USER_NODE_ID)
&& (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核 && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核
|| BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核 || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
&& ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { && ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
.setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason())); .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
@ -1456,7 +1513,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
} }
// 任务后置通知 // 任务后置通知
if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())){ if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())) {
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting(); BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting();
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());

View File

@ -106,14 +106,14 @@ public class CrmClueServiceImpl implements CrmClueService {
// 3. 记录操作日志上下文 // 3. 记录操作日志上下文
updateReqVO.setOwnerUserId(oldClue.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况 updateReqVO.setOwnerUserId(oldClue.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmCustomerSaveReqVO.class)); LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmClueSaveReqVO.class));
LogRecordContext.putVariable("clueName", oldClue.getName()); LogRecordContext.putVariable("clueName", oldClue.getName());
} }
private void validateRelationDataExists(CrmClueSaveReqVO reqVO) { private void validateRelationDataExists(CrmClueSaveReqVO reqVO) {
// 校验负责人 // 校验负责人
if (Objects.nonNull(reqVO.getOwnerUserId()) && if (Objects.nonNull(reqVO.getOwnerUserId()) &&
Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()).getCheckedData())) { Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()))) {
throw exception(USER_NOT_EXISTS); throw exception(USER_NOT_EXISTS);
} }
} }

View File

@ -159,10 +159,10 @@ public class CrmContactServiceImpl implements CrmContactService {
// 2. 删除联系人 // 2. 删除联系人
contactMapper.deleteById(id); contactMapper.deleteById(id);
// 4.1 删除数据权限 // 4.1 删除商机关联
permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
// 4.2 删除商机关联
contactBusinessService.deleteContactBusinessByContactId(id); contactBusinessService.deleteContactBusinessByContactId(id);
// 4.2 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
// 记录操作日志上下文 // 记录操作日志上下文
LogRecordContext.putVariable("contactName", contact.getName()); LogRecordContext.putVariable("contactName", contact.getName());

View File

@ -43,7 +43,7 @@ public class FileController {
@PostMapping("/upload") @PostMapping("/upload")
@Operation(summary = "上传文件", description = "模式一:后端上传文件") @Operation(summary = "上传文件", description = "模式一:后端上传文件")
public CommonResult<String> uploadFile(FileUploadReqVO uploadReqVO) throws Exception { public CommonResult<String> uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception {
MultipartFile file = uploadReqVO.getFile(); MultipartFile file = uploadReqVO.getFile();
byte[] content = IoUtil.readBytes(file.getInputStream()); byte[] content = IoUtil.readBytes(file.getInputStream());
return success(fileService.createFile(content, file.getOriginalFilename(), return success(fileService.createFile(content, file.getOriginalFilename(),

View File

@ -170,6 +170,7 @@
await this.#[[$modal]]#.confirm('是否确认删除?') await this.#[[$modal]]#.confirm('是否确认删除?')
try { try {
await ${simpleClassName}Api.delete${subSimpleClassName}List(this.checkedIds); await ${simpleClassName}Api.delete${subSimpleClassName}List(this.checkedIds);
this.checkedIds = [];
await this.getList(); await this.getList();
this.#[[$modal]]#.msgSuccess("删除成功"); this.#[[$modal]]#.msgSuccess("删除成功");
} catch {} } catch {}

View File

@ -338,6 +338,7 @@ export default {
await this.#[[$modal]]#.confirm('是否确认删除?') await this.#[[$modal]]#.confirm('是否确认删除?')
try { try {
await ${simpleClassName}Api.delete${simpleClassName}List(this.checkedIds); await ${simpleClassName}Api.delete${simpleClassName}List(this.checkedIds);
this.checkedIds = [];
await this.getList(); await this.getList();
this.#[[$modal]]#.msgSuccess("删除成功"); this.#[[$modal]]#.msgSuccess("删除成功");
} catch {} } catch {}

View File

@ -209,6 +209,7 @@ const handleDeleteBatch = async () => {
// 删除的二次确认 // 删除的二次确认
await message.delConfirm() await message.delConfirm()
await ${simpleClassName}Api.delete${subSimpleClassName}List(checkedIds.value); await ${simpleClassName}Api.delete${subSimpleClassName}List(checkedIds.value);
checkedIds.value = [];
message.success(t('common.delSuccess')) message.success(t('common.delSuccess'))
await getList(); await getList();
} catch {} } catch {}

View File

@ -366,6 +366,7 @@ const handleDeleteBatch = async () => {
// 删除的二次确认 // 删除的二次确认
await message.delConfirm() await message.delConfirm()
await ${simpleClassName}Api.delete${simpleClassName}List(checkedIds.value); await ${simpleClassName}Api.delete${simpleClassName}List(checkedIds.value);
checkedIds.value = [];
message.success(t('common.delSuccess')) message.success(t('common.delSuccess'))
await getList(); await getList();
} catch {} } catch {}

View File

@ -168,6 +168,7 @@ async function handleDeleteBatch() {
}); });
try { try {
await delete${simpleClassName}List(checkedIds.value); await delete${simpleClassName}List(checkedIds.value);
checkedIds.value = [];
message.success( $t('ui.actionMessage.deleteSuccess') ); message.success( $t('ui.actionMessage.deleteSuccess') );
await getList(); await getList();
} finally { } finally {

View File

@ -92,6 +92,7 @@ async function handleDeleteBatch() {
}); });
try { try {
await delete${subSimpleClassName}List(checkedIds.value); await delete${subSimpleClassName}List(checkedIds.value);
checkedIds.value = [];
message.success( $t('ui.actionMessage.deleteSuccess') ); message.success( $t('ui.actionMessage.deleteSuccess') );
await getList(); await getList();
} finally { } finally {

View File

@ -102,6 +102,7 @@ async function handleDeleteBatch() {
}); });
try { try {
await delete${simpleClassName}List(checkedIds.value); await delete${simpleClassName}List(checkedIds.value);
checkedIds.value = [];
message.success({ message.success({
content: $t('ui.actionMessage.deleteSuccess'), content: $t('ui.actionMessage.deleteSuccess'),
key: 'action_key_msg', key: 'action_key_msg',

View File

@ -82,6 +82,7 @@ async function handleDeleteBatch() {
}); });
try { try {
await delete${subSimpleClassName}List(checkedIds.value); await delete${subSimpleClassName}List(checkedIds.value);
checkedIds.value = [];
message.success({ message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.id]), content: $t('ui.actionMessage.deleteSuccess', [row.id]),
key: 'action_key_msg', key: 'action_key_msg',

View File

@ -163,6 +163,7 @@ async function handleDeleteBatch() {
}); });
try { try {
await delete${simpleClassName}List(checkedIds.value); await delete${simpleClassName}List(checkedIds.value);
checkedIds.value = [];
ElMessage.success($t('ui.actionMessage.deleteSuccess')); ElMessage.success($t('ui.actionMessage.deleteSuccess'));
await getList(); await getList();
} finally { } finally {

View File

@ -87,6 +87,7 @@ async function handleDeleteBatch() {
}); });
try { try {
await delete${subSimpleClassName}List(checkedIds.value); await delete${subSimpleClassName}List(checkedIds.value);
checkedIds.value = [];
ElMessage.success($t('ui.actionMessage.deleteSuccess')); ElMessage.success($t('ui.actionMessage.deleteSuccess'));
await getList(); await getList();
} finally { } finally {

View File

@ -99,6 +99,7 @@ async function handleDeleteBatch() {
}); });
try { try {
await delete${simpleClassName}List(checkedIds.value); await delete${simpleClassName}List(checkedIds.value);
checkedIds.value = [];
ElMessage.success($t('ui.actionMessage.deleteSuccess')); ElMessage.success($t('ui.actionMessage.deleteSuccess'));
onRefresh(); onRefresh();
} finally { } finally {

View File

@ -79,6 +79,7 @@ async function handleDeleteBatch() {
}); });
try { try {
await delete${subSimpleClassName}List(checkedIds.value); await delete${subSimpleClassName}List(checkedIds.value);
checkedIds.value = [];
ElMessage.success($t('ui.actionMessage.deleteSuccess')); ElMessage.success($t('ui.actionMessage.deleteSuccess'));
onRefresh(); onRefresh();
} finally { } finally {

View File

@ -28,6 +28,9 @@ public class MpMessagePageReqVO extends PageParam {
@Schema(description = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") @Schema(description = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
private String openid; private String openid;
@Schema(description = "公众号粉丝 UserId", example = "1")
private String userId;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间") @Schema(description = "创建时间")
private LocalDateTime[] createTime; private LocalDateTime[] createTime;

View File

@ -15,6 +15,7 @@ public interface MpMessageMapper extends BaseMapperX<MpMessageDO> {
.eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId()) .eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId())
.eqIfPresent(MpMessageDO::getType, reqVO.getType()) .eqIfPresent(MpMessageDO::getType, reqVO.getType())
.eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenid()) .eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenid())
.eqIfPresent(MpMessageDO::getUserId, reqVO.getUserId())
.betweenIfPresent(MpMessageDO::getCreateTime, reqVO.getCreateTime()) .betweenIfPresent(MpMessageDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(MpMessageDO::getId)); .orderByDesc(MpMessageDO::getId));
} }

View File

@ -585,7 +585,7 @@ public class PayOrderServiceImpl implements PayOrderService {
log.error("[expireOrder][order({}) 更新为支付关闭失败]", order.getId()); log.error("[expireOrder][order({}) 更新为支付关闭失败]", order.getId());
return false; return false;
} }
log.info("[expireOrder][order({}) 更新为支付关闭失败]", order.getId()); log.info("[expireOrder][order({}) 更新为支付关闭成功]", order.getId());
return true; return true;
} catch (Throwable e) { } catch (Throwable e) {
log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e); log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e);

View File

@ -1,27 +1,50 @@
package cn.iocoder.yudao.module.system.api.mail.dto; package cn.iocoder.yudao.module.system.api.mail.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Map; import java.util.Map;
@Schema(description = "RPC 服务 - 邮件发送给 Admin 或者 Member 用户 Request DTO") /**
* Request DTO
*
* @author wangjingqi
*/
@Data @Data
public class MailSendSingleToUserReqDTO { public class MailSendSingleToUserReqDTO {
@Schema(description = "用户编号", example = "1024") /**
*
*
* {@link #toMails}
*/
private Long userId; private Long userId;
@Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300")
@Email
private String mail;
@Schema(description = "邮件模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "USER_SEND") /**
*
*/
private List<@Email String> toMails;
/**
*
*/
private List<@Email String> ccMails;
/**
*
*/
private List<@Email String> bccMails;
/**
*
*/
@NotNull(message = "邮件模板编号不能为空") @NotNull(message = "邮件模板编号不能为空")
private String templateCode; private String templateCode;
/**
@Schema(description = "邮件模板参数") *
*/
private Map<String, Object> templateParams; private Map<String, Object> templateParams;
} }

View File

@ -19,13 +19,15 @@ public class MailSendApiImpl implements MailSendApi {
@Override @Override
public CommonResult<Long> sendSingleMailToAdmin(MailSendSingleToUserReqDTO reqDTO) { public CommonResult<Long> sendSingleMailToAdmin(MailSendSingleToUserReqDTO reqDTO) {
return success(mailSendService.sendSingleMailToAdmin(reqDTO.getMail(), reqDTO.getUserId(), return success(mailSendService.sendSingleMailToAdmin(reqDTO.getUserId(),
reqDTO.getToMails(), reqDTO.getCcMails(), reqDTO.getBccMails(),
reqDTO.getTemplateCode(), reqDTO.getTemplateParams())); reqDTO.getTemplateCode(), reqDTO.getTemplateParams()));
} }
@Override @Override
public CommonResult<Long> sendSingleMailToMember(MailSendSingleToUserReqDTO reqDTO) { public CommonResult<Long> sendSingleMailToMember(MailSendSingleToUserReqDTO reqDTO) {
return success(mailSendService.sendSingleMailToMember(reqDTO.getMail(), reqDTO.getUserId(), return success(mailSendService.sendSingleMailToMember(reqDTO.getUserId(),
reqDTO.getToMails(), reqDTO.getCcMails(), reqDTO.getBccMails(),
reqDTO.getTemplateCode(), reqDTO.getTemplateParams())); reqDTO.getTemplateCode(), reqDTO.getTemplateParams()));
} }

View File

@ -11,6 +11,24 @@ tag: Yunai.local
"code": "1024" "code": "1024"
} }
### 请求 /login 接口【加密 AES】 => 成功
POST {{baseUrl}}/system/auth/login
Content-Type: application/json
tenant-id: {{adminTenantId}}
tag: Yunai.local
X-API-ENCRYPT: true
WvSX9MOrenyGfBhEM0g1/hHgq8ocktMZ9OwAJ6MOG5FUrzYF/rG5JF1eMptQM1wT73VgDS05l/37WeRtad+JrqChAul/sR/SdOsUKqjBhvvQx1JVhzxr6s8uUP67aKTSZ6Psv7O32ELxXrzSaQvG5CInzz3w6sLtbNNLd1kXe6Q=
### 请求 /login 接口【加密 RSA】 => 成功
POST {{baseUrl}}/system/auth/login
Content-Type: application/json
tenant-id: {{adminTenantId}}
tag: Yunai.local
X-API-ENCRYPT: true
e7QZTork9ZV5CmgZvSd+cHZk3xdUxKtowLM02kOha+gxHK2H/daU8nVBYS3+bwuDRy5abf+Pz1QJJGVAEd27wwrXBmupOOA/bhpuzzDwcRuJRD+z+YgiNoEXFDRHERxPYlPqAe9zAHtihD0ceub1AjybQsEsROew4C3Q602XYW0=
### 请求 /login 接口 => 成功(无验证码) ### 请求 /login 接口 => 成功(无验证码)
POST {{baseUrl}}/system/auth/login POST {{baseUrl}}/system/auth/login
Content-Type: application/json Content-Type: application/json
@ -21,16 +39,6 @@ tenant-id: {{adminTenantId}}
"password": "admin123" "password": "admin123"
} }
### 请求 /login 接口 => 失败(租户不存在)
POST {{baseUrl}}/system/auth/login
Content-Type: application/json
tenant-id: 2
{
"username": "admin",
"password": "admin123"
}
### 请求 /get-permission-info 接口 => 成功 ### 请求 /get-permission-info 接口 => 成功
GET {{baseUrl}}/system/auth/get-permission-info GET {{baseUrl}}/system/auth/get-permission-info
Authorization: Bearer {{token}} Authorization: Bearer {{token}}

View File

@ -56,6 +56,15 @@ public class DeptController {
return success(true); return success(true);
} }
@DeleteMapping("/delete-list")
@Operation(summary = "批量删除部门")
@Parameter(name = "ids", description = "编号列表", required = true)
@PreAuthorize("@ss.hasPermission('system:dept:delete')")
public CommonResult<Boolean> deleteDeptList(@RequestParam("ids") List<Long> ids) {
deptService.deleteDeptList(ids);
return success(true);
}
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "获取部门列表") @Operation(summary = "获取部门列表")
@PreAuthorize("@ss.hasPermission('system:dept:query')") @PreAuthorize("@ss.hasPermission('system:dept:query')")

View File

@ -91,7 +91,8 @@ public class MailTemplateController {
@Operation(summary = "发送短信") @Operation(summary = "发送短信")
@PreAuthorize("@ss.hasPermission('system:mail-template:send-mail')") @PreAuthorize("@ss.hasPermission('system:mail-template:send-mail')")
public CommonResult<Long> sendMail(@Valid @RequestBody MailTemplateSendReqVO sendReqVO) { public CommonResult<Long> sendMail(@Valid @RequestBody MailTemplateSendReqVO sendReqVO) {
return success(mailSendService.sendSingleMailToAdmin(sendReqVO.getMail(), getLoginUserId(), return success(mailSendService.sendSingleMailToAdmin(getLoginUserId(),
sendReqVO.getToMails(), sendReqVO.getCcMails(), sendReqVO.getBccMails(),
sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams())); sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));
} }

View File

@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.Map; import java.util.Map;
@Schema(description = "管理后台 - 邮件日志 Response VO") @Schema(description = "管理后台 - 邮件日志 Response VO")
@ -19,8 +20,14 @@ public class MailLogRespVO {
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", example = "2") @Schema(description = "用户类型,参见 UserTypeEnum 枚举", example = "2")
private Byte userType; private Byte userType;
@Schema(description = "接收邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "76854@qq.com") @Schema(description = "接收邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user1@example.com, user2@example.com")
private String toMail; private List<String> toMails;
@Schema(description = "抄送邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user3@example.com, user4@example.com")
private List<String> ccMails;
@Schema(description = "密送邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user5@example.com, user6@example.com")
private List<String> bccMails;
@Schema(description = "邮箱账号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18107") @Schema(description = "邮箱账号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18107")
private Long accountId; private Long accountId;

View File

@ -5,15 +5,22 @@ import lombok.Data;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Map; import java.util.Map;
@Schema(description = "管理后台 - 邮件发送 Req VO") @Schema(description = "管理后台 - 邮件发送 Req VO")
@Data @Data
public class MailTemplateSendReqVO { public class MailTemplateSendReqVO {
@Schema(description = "接收邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "7685413@qq.com") @Schema(description = "接收邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "[user1@example.com, user2@example.com]")
@NotEmpty(message = "接收邮箱不能为空") @NotEmpty(message = "接收邮箱不能为空")
private String mail; private List<String> toMails;
@Schema(description = "抄送邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "[user3@example.com, user4@example.com]")
private List<String> ccMails;
@Schema(description = "密送邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "[user5@example.com, user6@example.com]")
private List<String> bccMails;
@Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01") @Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01")
@NotNull(message = "模板编码不能为空") @NotNull(message = "模板编码不能为空")

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.mail;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum; import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;
@ -12,6 +13,7 @@ import lombok.*;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -47,10 +49,22 @@ public class MailLogDO extends BaseDO implements Serializable {
* {@link UserTypeEnum} * {@link UserTypeEnum}
*/ */
private Integer userType; private Integer userType;
/** /**
* *
*/ */
private String toMail; @TableField(typeHandler = StringListTypeHandler.class)
private List<String> toMails;
/**
*
*/
@TableField(typeHandler = StringListTypeHandler.class)
private List<String> ccMails;
/**
*
*/
@TableField(typeHandler = StringListTypeHandler.class)
private List<String> bccMails;
/** /**
* *

View File

@ -1,8 +1,10 @@
package cn.iocoder.yudao.module.system.dal.mysql.mail; package cn.iocoder.yudao.module.system.dal.mysql.mail;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@ -14,11 +16,12 @@ public interface MailLogMapper extends BaseMapperX<MailLogDO> {
return selectPage(reqVO, new LambdaQueryWrapperX<MailLogDO>() return selectPage(reqVO, new LambdaQueryWrapperX<MailLogDO>()
.eqIfPresent(MailLogDO::getUserId, reqVO.getUserId()) .eqIfPresent(MailLogDO::getUserId, reqVO.getUserId())
.eqIfPresent(MailLogDO::getUserType, reqVO.getUserType()) .eqIfPresent(MailLogDO::getUserType, reqVO.getUserType())
.likeIfPresent(MailLogDO::getToMail, reqVO.getToMail())
.eqIfPresent(MailLogDO::getAccountId, reqVO.getAccountId()) .eqIfPresent(MailLogDO::getAccountId, reqVO.getAccountId())
.eqIfPresent(MailLogDO::getTemplateId, reqVO.getTemplateId()) .eqIfPresent(MailLogDO::getTemplateId, reqVO.getTemplateId())
.eqIfPresent(MailLogDO::getSendStatus, reqVO.getSendStatus()) .eqIfPresent(MailLogDO::getSendStatus, reqVO.getSendStatus())
.betweenIfPresent(MailLogDO::getSendTime, reqVO.getSendTime()) .betweenIfPresent(MailLogDO::getSendTime, reqVO.getSendTime())
.apply(StrUtil.isNotBlank(reqVO.getToMail()),
MyBatisUtils.findInSet("to_mails", reqVO.getToMail()))
.orderByDesc(MailLogDO::getId)); .orderByDesc(MailLogDO::getId));
} }

View File

@ -5,6 +5,9 @@ import lombok.Data;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.util.Collection;
import java.util.List;
/** /**
* *
* *
@ -21,8 +24,16 @@ public class MailSendMessage {
/** /**
* *
*/ */
@NotNull(message = "接收邮件地址不能为空") @NotEmpty(message = "接收邮件地址不能为空")
private String mail; private Collection<String> toMails;
/**
*
*/
private Collection<String> ccMails;
/**
*
*/
private Collection<String> bccMails;
/** /**
* *
*/ */

View File

@ -7,6 +7,11 @@ import org.springframework.stereotype.Component;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import java.util.Collection;
import java.util.List;
import static java.util.Collections.singletonList;
/** /**
* Mail Producer * Mail Producer
* *
@ -24,17 +29,22 @@ public class MailProducer {
* {@link MailSendMessage} * {@link MailSendMessage}
* *
* @param sendLogId * @param sendLogId
* @param mail * @param toMails
* @param ccMails
* @param bccMails
* @param accountId * @param accountId
* @param nickname * @param nickname
* @param title * @param title
* @param content * @param content
*/ */
public void sendMailSendMessage(Long sendLogId, String mail, Long accountId, public void sendMailSendMessage(Long sendLogId,
String nickname, String title, String content) { Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
Long accountId, String nickname, String title, String content) {
MailSendMessage message = new MailSendMessage() MailSendMessage message = new MailSendMessage()
.setLogId(sendLogId).setMail(mail).setAccountId(accountId) .setLogId(sendLogId)
.setNickname(nickname).setTitle(title).setContent(content); .setToMails(toMails).setCcMails(ccMails).setBccMails(bccMails)
.setAccountId(accountId).setNickname(nickname)
.setTitle(title).setContent(content);
applicationContext.publishEvent(message); applicationContext.publishEvent(message);
} }

View File

@ -36,6 +36,13 @@ public interface DeptService {
*/ */
void deleteDept(Long id); void deleteDept(Long id);
/**
*
*
* @param ids
*/
void deleteDeptList(List<Long> ids);
/** /**
* *
* *

View File

@ -88,6 +88,21 @@ public class DeptServiceImpl implements DeptService {
deptMapper.deleteById(id); deptMapper.deleteById(id);
} }
@Override
@CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
public void deleteDeptList(List<Long> ids) {
// 校验是否有子部门
for (Long id : ids) {
if (deptMapper.selectCountByParentId(id) > 0) {
throw exception(DEPT_EXITS_CHILDREN);
}
}
// 批量删除部门
deptMapper.deleteByIds(ids);
}
@VisibleForTesting @VisibleForTesting
void validateDeptExists(Long id) { void validateDeptExists(Long id) {
if (id == null) { if (id == null) {

View File

@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -35,18 +37,21 @@ public interface MailLogService {
/** /**
* *
* *
* @param userId * @param userId
* @param userType * @param userType
* @param toMail * @param toMails
* @param account * @param ccMails
* @param template * @param bccMails
* @param account
* @param template
* @param templateContent * @param templateContent
* @param templateParams * @param templateParams
* @param isSend * @param isSend
* @return * @return
*/ */
Long createMailLog(Long userId, Integer userType, String toMail, Long createMailLog(Long userId, Integer userType,
MailAccountDO account, MailTemplateDO template , Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
MailAccountDO account, MailTemplateDO template,
String templateContent, Map<String, Object> templateParams, Boolean isSend); String templateContent, Map<String, Object> templateParams, Boolean isSend);
/** /**

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.system.service.mail; package cn.iocoder.yudao.module.system.service.mail;
import cn.hutool.core.collection.ListUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
@ -12,8 +13,7 @@ import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Map; import java.util.*;
import java.util.Objects;
import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage; import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage;
@ -41,7 +41,8 @@ public class MailLogServiceImpl implements MailLogService {
} }
@Override @Override
public Long createMailLog(Long userId, Integer userType, String toMail, public Long createMailLog(Long userId, Integer userType,
Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
MailAccountDO account, MailTemplateDO template, MailAccountDO account, MailTemplateDO template,
String templateContent, Map<String, Object> templateParams, Boolean isSend) { String templateContent, Map<String, Object> templateParams, Boolean isSend) {
MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder(); MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder();
@ -49,7 +50,8 @@ public class MailLogServiceImpl implements MailLogService {
logDOBuilder.sendStatus(Objects.equals(isSend, true) ? MailSendStatusEnum.INIT.getStatus() logDOBuilder.sendStatus(Objects.equals(isSend, true) ? MailSendStatusEnum.INIT.getStatus()
: MailSendStatusEnum.IGNORE.getStatus()) : MailSendStatusEnum.IGNORE.getStatus())
// 用户信息 // 用户信息
.userId(userId).userType(userType).toMail(toMail) .userId(userId).userType(userType)
.toMails(ListUtil.toList(toMails)).ccMails(ListUtil.toList(ccMails)).bccMails(ListUtil.toList(bccMails))
.accountId(account.getId()).fromMail(account.getMail()) .accountId(account.getId()).fromMail(account.getMail())
// 模板相关字段 // 模板相关字段
.templateId(template.getId()).templateCode(template.getCode()).templateNickname(template.getNickname()) .templateId(template.getId()).templateCode(template.getCode()).templateNickname(template.getNickname())

View File

@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.system.service.mail; package cn.iocoder.yudao.module.system.service.mail;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage; import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
import java.util.Collection;
import java.util.Map; import java.util.Map;
/** /**
@ -15,38 +17,53 @@ public interface MailSendService {
/** /**
* *
* *
* @param mail
* @param userId * @param userId
* @param toMails
* @param ccMails
* @param bccMails
* @param templateCode * @param templateCode
* @param templateParams * @param templateParams
* @return * @return
*/ */
Long sendSingleMailToAdmin(String mail, Long userId, default Long sendSingleMailToAdmin(Long userId,
String templateCode, Map<String, Object> templateParams); Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
String templateCode, Map<String, Object> templateParams) {
return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.ADMIN.getValue(),
templateCode, templateParams);
}
/** /**
* APP * APP
* *
* @param mail
* @param userId * @param userId
* @param toMails
* @param ccMails
* @param bccMails
* @param templateCode * @param templateCode
* @param templateParams * @param templateParams
* @return * @return
*/ */
Long sendSingleMailToMember(String mail, Long userId, default Long sendSingleMailToMember(Long userId,
String templateCode, Map<String, Object> templateParams); Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
String templateCode, Map<String, Object> templateParams) {
return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.MEMBER.getValue(),
templateCode, templateParams);
}
/** /**
* *
* *
* @param mail * @param toMails
* @param userId * @param ccMails
* @param bccMails
* @param userId
* @param userType * @param userType
* @param templateCode * @param templateCode
* @param templateParams * @param templateParams
* @return * @return
*/ */
Long sendSingleMail(String mail, Long userId, Integer userType, Long sendSingleMail(Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
Long userId, Integer userType,
String templateCode, Map<String, Object> templateParams); String templateCode, Map<String, Object> templateParams);
/** /**

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.system.service.mail; package cn.iocoder.yudao.module.system.service.mail;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
@ -13,10 +15,13 @@ import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.extra.mail.*; import org.dromara.hutool.extra.mail.MailAccount;
import org.dromara.hutool.extra.mail.MailUtil;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -49,56 +54,67 @@ public class MailSendServiceImpl implements MailSendService {
private MailProducer mailProducer; private MailProducer mailProducer;
@Override @Override
public Long sendSingleMailToAdmin(String mail, Long userId, public Long sendSingleMail(Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
String templateCode, Map<String, Object> templateParams) { Long userId, Integer userType,
// 如果 mail 为空,则加载用户编号对应的邮箱
if (StrUtil.isEmpty(mail)) {
AdminUserDO user = adminUserService.getUser(userId);
if (user != null) {
mail = user.getEmail();
}
}
// 执行发送
return sendSingleMail(mail, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);
}
@Override
public Long sendSingleMailToMember(String mail, Long userId,
String templateCode, Map<String, Object> templateParams) {
// 如果 mail 为空,则加载用户编号对应的邮箱
if (StrUtil.isEmpty(mail)) {
mail = memberService.getMemberUserEmail(userId);
}
// 执行发送
return sendSingleMail(mail, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams);
}
@Override
public Long sendSingleMail(String mail, Long userId, Integer userType,
String templateCode, Map<String, Object> templateParams) { String templateCode, Map<String, Object> templateParams) {
// 校验邮箱模版是否合法 // 1.1 校验邮箱模版是否合法
MailTemplateDO template = validateMailTemplate(templateCode); MailTemplateDO template = validateMailTemplate(templateCode);
// 校验邮箱账号是否合法 // 1.2 校验邮箱账号是否合法
MailAccountDO account = validateMailAccount(template.getAccountId()); MailAccountDO account = validateMailAccount(template.getAccountId());
// 1.3 校验邮件参数是否缺失
// 校验邮箱是否存在
mail = validateMail(mail);
validateTemplateParams(template, templateParams); validateTemplateParams(template, templateParams);
// 2. 组装邮箱
String userMail = getUserMail(userId, userType);
Collection<String> toMailSet = new LinkedHashSet<>();
Collection<String> ccMailSet = new LinkedHashSet<>();
Collection<String> bccMailSet = new LinkedHashSet<>();
if (Validator.isEmail(userMail)) {
toMailSet.add(userMail);
}
if (CollUtil.isNotEmpty(toMails)) {
toMails.stream().filter(Validator::isEmail).forEach(toMailSet::add);
}
if (CollUtil.isNotEmpty(ccMails)) {
ccMails.stream().filter(Validator::isEmail).forEach(ccMailSet::add);
}
if (CollUtil.isNotEmpty(bccMails)) {
bccMails.stream().filter(Validator::isEmail).forEach(bccMailSet::add);
}
if (CollUtil.isEmpty(toMailSet)) {
throw exception(MAIL_SEND_MAIL_NOT_EXISTS);
}
// 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()); Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus());
String title = mailTemplateService.formatMailTemplateContent(template.getTitle(), templateParams); String title = mailTemplateService.formatMailTemplateContent(template.getTitle(), templateParams);
String content = mailTemplateService.formatMailTemplateContent(template.getContent(), templateParams); String content = mailTemplateService.formatMailTemplateContent(template.getContent(), templateParams);
Long sendLogId = mailLogService.createMailLog(userId, userType, mail, Long sendLogId = mailLogService.createMailLog(userId, userType, toMailSet, ccMailSet, bccMailSet,
account, template, content, templateParams, isSend); account, template, content, templateParams, isSend);
// 发送 MQ 消息,异步执行发送短信 // 发送 MQ 消息,异步执行发送短信
if (isSend) { if (isSend) {
mailProducer.sendMailSendMessage(sendLogId, mail, account.getId(), mailProducer.sendMailSendMessage(sendLogId, toMailSet, ccMailSet, bccMailSet,
template.getNickname(), title, content); account.getId(), template.getNickname(), title, content);
} }
return sendLogId; return sendLogId;
} }
private String getUserMail(Long userId, Integer userType) {
if (userId == null || userType == null) {
return null;
}
if (UserTypeEnum.ADMIN.getValue().equals(userType)) {
AdminUserDO user = adminUserService.getUser(userId);
if (user != null) {
return user.getEmail();
}
}
if (UserTypeEnum.MEMBER.getValue().equals(userType)) {
return memberService.getMemberUserEmail(userId);
}
return null;
}
@Override @Override
public void doSendMail(MailSendMessage message) { public void doSendMail(MailSendMessage message) {
// 1. 创建发送账号 // 1. 创建发送账号
@ -106,7 +122,7 @@ public class MailSendServiceImpl implements MailSendService {
MailAccount mailAccount = buildMailAccount(account, message.getNickname()); MailAccount mailAccount = buildMailAccount(account, message.getNickname());
// 2. 发送邮件 // 2. 发送邮件
try { try {
String messageId = MailUtil.send(mailAccount, message.getMail(), String messageId = MailUtil.send(mailAccount, message.getToMails(), message.getCcMails(), message.getBccMails(),
message.getTitle(), message.getContent(), true); message.getTitle(), message.getContent(), true);
// 3. 更新结果(成功) // 3. 更新结果(成功)
mailLogService.updateMailSendResult(message.getLogId(), messageId, null); mailLogService.updateMailSendResult(message.getLogId(), messageId, null);
@ -146,16 +162,8 @@ public class MailSendServiceImpl implements MailSendService {
return account; return account;
} }
@VisibleForTesting
String validateMail(String mail) {
if (StrUtil.isEmpty(mail)) {
throw exception(MAIL_SEND_MAIL_NOT_EXISTS);
}
return mail;
}
/** /**
* *
* *
* @param template * @param template
* @param templateParams * @param templateParams

View File

@ -255,9 +255,6 @@ public class MenuServiceImpl implements MenuService {
return; return;
} }
// 如果 id 为空,说明不用比较是否为相同 id 的菜单 // 如果 id 为空,说明不用比较是否为相同 id 的菜单
if (id == null) {
throw exception(MENU_NAME_DUPLICATE);
}
if (!menu.getId().equals(id)) { if (!menu.getId().equals(id)) {
throw exception(MENU_NAME_DUPLICATE); throw exception(MENU_NAME_DUPLICATE);
} }

View File

@ -10,15 +10,17 @@ import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
import cn.iocoder.yudao.module.system.dal.mysql.mail.MailLogMapper; import cn.iocoder.yudao.module.system.dal.mysql.mail.MailLogMapper;
import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum; import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum;
import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import static cn.hutool.core.util.RandomUtil.randomEle; import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
@ -43,7 +45,9 @@ public class MailLogServiceImplTest extends BaseDbUnitTest {
// 准备参数 // 准备参数
Long userId = randomLongId(); Long userId = randomLongId();
Integer userType = randomEle(UserTypeEnum.values()).getValue(); Integer userType = randomEle(UserTypeEnum.values()).getValue();
String toMail = randomEmail(); Collection<String> toMails = Lists.newArrayList(randomEmail(), randomEmail());
Collection<String> ccMails = Lists.newArrayList(randomEmail());
Collection<String> bccMails = Lists.newArrayList(randomEmail());
MailAccountDO account = randomPojo(MailAccountDO.class); MailAccountDO account = randomPojo(MailAccountDO.class);
MailTemplateDO template = randomPojo(MailTemplateDO.class); MailTemplateDO template = randomPojo(MailTemplateDO.class);
String templateContent = randomString(); String templateContent = randomString();
@ -52,14 +56,20 @@ public class MailLogServiceImplTest extends BaseDbUnitTest {
// mock 方法 // mock 方法
// 调用 // 调用
Long logId = mailLogService.createMailLog(userId, userType, toMail, account, template, templateContent, templateParams, isSend); Long logId = mailLogService.createMailLog(userId, userType, toMails, ccMails, bccMails,
account, template, templateContent, templateParams, isSend);
// 断言 // 断言
MailLogDO log = mailLogMapper.selectById(logId); MailLogDO log = mailLogMapper.selectById(logId);
assertNotNull(log); assertNotNull(log);
assertEquals(MailSendStatusEnum.INIT.getStatus(), log.getSendStatus()); assertEquals(MailSendStatusEnum.INIT.getStatus(), log.getSendStatus());
assertEquals(userId, log.getUserId()); assertEquals(userId, log.getUserId());
assertEquals(userType, log.getUserType()); assertEquals(userType, log.getUserType());
assertEquals(toMail, log.getToMail()); assertEquals(toMails.size(), log.getToMails().size());
assertTrue(log.getToMails().containsAll(toMails));
assertEquals(ccMails.size(), log.getCcMails().size());
assertTrue(log.getCcMails().containsAll(ccMails));
assertEquals(bccMails.size(), log.getBccMails().size());
assertTrue(log.getBccMails().containsAll(bccMails));
assertEquals(account.getId(), log.getAccountId()); assertEquals(account.getId(), log.getAccountId());
assertEquals(account.getMail(), log.getFromMail()); assertEquals(account.getMail(), log.getFromMail());
assertEquals(template.getId(), log.getTemplateId()); assertEquals(template.getId(), log.getTemplateId());
@ -132,48 +142,50 @@ public class MailLogServiceImplTest extends BaseDbUnitTest {
@Test @Test
public void testGetMailLogPage() { public void testGetMailLogPage() {
// mock 数据 // mock 数据
MailLogDO dbMailLog = randomPojo(MailLogDO.class, o -> { // 等会查询到 MailLogDO dbMailLog = randomPojo(MailLogDO.class, o -> { // 等会查询到
o.setUserId(1L); o.setUserId(1L);
o.setUserType(UserTypeEnum.ADMIN.getValue()); o.setUserType(UserTypeEnum.ADMIN.getValue());
o.setToMail("768@qq.com"); o.setToMails(Lists.newArrayList("768@qq.com"));
o.setAccountId(10L); o.setCcMails(Lists.newArrayList());
o.setTemplateId(100L); o.setBccMails(Lists.newArrayList());
o.setSendStatus(MailSendStatusEnum.INIT.getStatus()); o.setAccountId(10L);
o.setSendTime(buildTime(2023, 2, 10)); o.setTemplateId(100L);
o.setTemplateParams(randomTemplateParams()); o.setSendStatus(MailSendStatusEnum.INIT.getStatus());
}); o.setSendTime(buildTime(2023, 2, 10));
mailLogMapper.insert(dbMailLog); o.setTemplateParams(randomTemplateParams());
// 测试 userId 不匹配 });
mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserId(2L))); mailLogMapper.insert(dbMailLog);
// 测试 userType 不匹配 // 测试 userId 不匹配
mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserType(UserTypeEnum.MEMBER.getValue()))); mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserId(2L)));
// 测试 toMail 不匹配 // 测试 userType 不匹配
mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setToMail("788@.qq.com"))); mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));
// 测试 accountId 不匹配 // 测试 toMails 不匹配特殊find_in_set 无法单测)
mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setAccountId(11L))); // mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setToMails(Lists.newArrayList("788@qq.com"))));
// 测试 templateId 不匹配 // 测试 accountId 不匹配
mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setTemplateId(101L))); mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setAccountId(11L)));
// 测试 sendStatus 不匹配 // 测试 templateId 不匹配
mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setSendStatus(MailSendStatusEnum.SUCCESS.getStatus()))); mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setTemplateId(101L)));
// 测试 sendTime 不匹配 // 测试 sendStatus 不匹配
mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setSendTime(buildTime(2023, 3, 10)))); mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setSendStatus(MailSendStatusEnum.SUCCESS.getStatus())));
// 准备参数 // 测试 sendTime 不匹配
MailLogPageReqVO reqVO = new MailLogPageReqVO(); mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setSendTime(buildTime(2023, 3, 10))));
reqVO.setUserId(1L); // 准备参数
reqVO.setUserType(UserTypeEnum.ADMIN.getValue()); MailLogPageReqVO reqVO = new MailLogPageReqVO();
reqVO.setToMail("768"); reqVO.setUserId(1L);
reqVO.setAccountId(10L); reqVO.setUserType(UserTypeEnum.ADMIN.getValue());
reqVO.setTemplateId(100L); // reqVO.setToMail("768@qq.com");
reqVO.setSendStatus(MailSendStatusEnum.INIT.getStatus()); reqVO.setAccountId(10L);
reqVO.setSendTime((buildBetweenTime(2023, 2, 1, 2023, 2, 15))); reqVO.setTemplateId(100L);
reqVO.setSendStatus(MailSendStatusEnum.INIT.getStatus());
reqVO.setSendTime((buildBetweenTime(2023, 2, 1, 2023, 2, 15)));
// 调用 // 调用
PageResult<MailLogDO> pageResult = mailLogService.getMailLogPage(reqVO); PageResult<MailLogDO> pageResult = mailLogService.getMailLogPage(reqVO);
// 断言 // 断言
assertEquals(1, pageResult.getTotal()); assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size()); assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbMailLog, pageResult.getList().get(0)); assertPojoEquals(dbMailLog, pageResult.getList().get(0));
} }
private static Map<String, Object> randomTemplateParams() { private static Map<String, Object> randomTemplateParams() {

View File

@ -13,14 +13,14 @@ import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
import cn.iocoder.yudao.module.system.service.member.MemberService; import cn.iocoder.yudao.module.system.service.member.MemberService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import org.assertj.core.util.Lists; import org.assertj.core.util.Lists;
import org.dromara.hutool.extra.mail.MailAccount; import org.dromara.hutool.extra.mail.*;
import org.dromara.hutool.extra.mail.MailUtil;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -66,14 +66,18 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
} }
@Test @Test
public void testSendSingleMailToAdmin() { public void testSendSingleMail_success() {
// 准备参数 // 准备参数
Long userId = randomLongId(); Long userId = randomLongId();
String templateCode = RandomUtils.randomString(); String templateCode = RandomUtils.randomString();
Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234") Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234")
.put("op", "login").build(); .put("op", "login").build();
Collection<String> toMails = Lists.newArrayList("admin@test.com");
Collection<String> ccMails = Lists.newArrayList("cc@test.com");
Collection<String> bccMails = Lists.newArrayList("bcc@test.com");
// mock adminUserService 的方法 // mock adminUserService 的方法
AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setMobile("15601691300")); AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setEmail("admin@example.com"));
when(adminUserService.getUser(eq(userId))).thenReturn(user); when(adminUserService.getUser(eq(userId))).thenReturn(user);
// mock MailTemplateService 的方法 // mock MailTemplateService 的方法
@ -94,61 +98,27 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account); when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);
// mock MailLogService 的方法 // mock MailLogService 的方法
Long mailLogId = randomLongId(); Long mailLogId = randomLongId();
when(mailLogService.createMailLog(eq(userId), eq(UserTypeEnum.ADMIN.getValue()), eq(user.getEmail()), when(mailLogService.createMailLog(eq(userId), eq(UserTypeEnum.ADMIN.getValue()),
argThat(toMailSet -> toMailSet.contains(user.getEmail()) && toMailSet.contains("admin@test.com")),
argThat(ccMailSet -> ccMailSet.contains("cc@test.com")),
argThat(bccMailSet -> bccMailSet.contains("bcc@test.com")),
eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId); eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId);
// 调用 // 调用
Long resultMailLogId = mailSendService.sendSingleMailToAdmin(null, userId, templateCode, templateParams); Long resultMailLogId = mailSendService.sendSingleMail(toMails, ccMails, bccMails, userId,
UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);
// 断言 // 断言
assertEquals(mailLogId, resultMailLogId); assertEquals(mailLogId, resultMailLogId);
// 断言调用 // 断言调用
verify(mailProducer).sendMailSendMessage(eq(mailLogId), eq(user.getEmail()), verify(mailProducer).sendMailSendMessage(eq(mailLogId),
eq(account.getId()), eq(template.getNickname()), eq(title), eq(content)); argThat(toMailSet -> toMailSet.contains(user.getEmail()) && toMailSet.contains("admin@test.com")),
} argThat(ccMailSet -> ccMailSet.contains("cc@test.com")),
argThat(bccMailSet -> bccMailSet.contains("bcc@test.com")),
@Test
public void testSendSingleMailToMember() {
// 准备参数
Long userId = randomLongId();
String templateCode = RandomUtils.randomString();
Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234")
.put("op", "login").build();
// mock memberService 的方法
String mail = randomEmail();
when(memberService.getMemberUserEmail(eq(userId))).thenReturn(mail);
// mock MailTemplateService 的方法
MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> {
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setContent("验证码为{code}, 操作为{op}");
o.setParams(Lists.newArrayList("code", "op"));
});
when(mailTemplateService.getMailTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);
String title = RandomUtils.randomString();
when(mailTemplateService.formatMailTemplateContent(eq(template.getTitle()), eq(templateParams)))
.thenReturn(title);
String content = RandomUtils.randomString();
when(mailTemplateService.formatMailTemplateContent(eq(template.getContent()), eq(templateParams)))
.thenReturn(content);
// mock MailAccountService 的方法
MailAccountDO account = randomPojo(MailAccountDO.class);
when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);
// mock MailLogService 的方法
Long mailLogId = randomLongId();
when(mailLogService.createMailLog(eq(userId), eq(UserTypeEnum.MEMBER.getValue()), eq(mail),
eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId);
// 调用
Long resultMailLogId = mailSendService.sendSingleMailToMember(null, userId, templateCode, templateParams);
// 断言
assertEquals(mailLogId, resultMailLogId);
// 断言调用
verify(mailProducer).sendMailSendMessage(eq(mailLogId), eq(mail),
eq(account.getId()), eq(template.getNickname()), eq(title), eq(content)); eq(account.getId()), eq(template.getNickname()), eq(title), eq(content));
} }
/** /**
* *
*/ */
@Test @Test
public void testSendSingleMail_successWhenMailTemplateEnable() { public void testSendSingleMail_successWhenMailTemplateEnable() {
@ -159,6 +129,8 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
String templateCode = RandomUtils.randomString(); String templateCode = RandomUtils.randomString();
Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234") Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234")
.put("op", "login").build(); .put("op", "login").build();
Collection<String> toMails = Lists.newArrayList(mail);
// mock MailTemplateService 的方法 // mock MailTemplateService 的方法
MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> { MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> {
o.setStatus(CommonStatusEnum.ENABLE.getStatus()); o.setStatus(CommonStatusEnum.ENABLE.getStatus());
@ -177,23 +149,29 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account); when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);
// mock MailLogService 的方法 // mock MailLogService 的方法
Long mailLogId = randomLongId(); Long mailLogId = randomLongId();
when(mailLogService.createMailLog(eq(userId), eq(userType), eq(mail), when(mailLogService.createMailLog(eq(userId), eq(userType),
argThat(toMailSet -> toMailSet.contains(mail)),
argThat(Collection::isEmpty),
argThat(Collection::isEmpty),
eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId); eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId);
// 调用 // 调用
Long resultMailLogId = mailSendService.sendSingleMail(mail, userId, userType, templateCode, templateParams); Long resultMailLogId = mailSendService.sendSingleMail(toMails, null, null, userId, userType, templateCode, templateParams);
// 断言 // 断言
assertEquals(mailLogId, resultMailLogId); assertEquals(mailLogId, resultMailLogId);
// 断言调用 // 断言调用
verify(mailProducer).sendMailSendMessage(eq(mailLogId), eq(mail), verify(mailProducer).sendMailSendMessage(eq(mailLogId),
argThat(toMailSet -> toMailSet.contains(mail)),
argThat(Collection::isEmpty),
argThat(Collection::isEmpty),
eq(account.getId()), eq(template.getNickname()), eq(title), eq(content)); eq(account.getId()), eq(template.getNickname()), eq(title), eq(content));
} }
/** /**
* *
*/ */
@Test @Test
public void testSendSingleMail_successWhenSmsTemplateDisable() { public void testSendSingleMail_successWhenMailTemplateDisable() {
// 准备参数 // 准备参数
String mail = randomEmail(); String mail = randomEmail();
Long userId = randomLongId(); Long userId = randomLongId();
@ -201,6 +179,8 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
String templateCode = RandomUtils.randomString(); String templateCode = RandomUtils.randomString();
Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234") Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234")
.put("op", "login").build(); .put("op", "login").build();
Collection<String> toMails = Lists.newArrayList(mail);
// mock MailTemplateService 的方法 // mock MailTemplateService 的方法
MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> { MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> {
o.setStatus(CommonStatusEnum.DISABLE.getStatus()); o.setStatus(CommonStatusEnum.DISABLE.getStatus());
@ -219,15 +199,18 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account); when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);
// mock MailLogService 的方法 // mock MailLogService 的方法
Long mailLogId = randomLongId(); Long mailLogId = randomLongId();
when(mailLogService.createMailLog(eq(userId), eq(userType), eq(mail), when(mailLogService.createMailLog(eq(userId), eq(userType),
argThat(toMailSet -> toMailSet.contains(mail)),
argThat(Collection::isEmpty),
argThat(Collection::isEmpty),
eq(account), eq(template), eq(content), eq(templateParams), eq(false))).thenReturn(mailLogId); eq(account), eq(template), eq(content), eq(templateParams), eq(false))).thenReturn(mailLogId);
// 调用 // 调用
Long resultMailLogId = mailSendService.sendSingleMail(mail, userId, userType, templateCode, templateParams); Long resultMailLogId = mailSendService.sendSingleMail(toMails, null, null, userId, userType, templateCode, templateParams);
// 断言 // 断言
assertEquals(mailLogId, resultMailLogId); assertEquals(mailLogId, resultMailLogId);
// 断言调用 // 断言调用
verify(mailProducer, times(0)).sendMailSendMessage(anyLong(), anyString(), verify(mailProducer, times(0)).sendMailSendMessage(anyLong(), any(), any(), any(),
anyLong(), anyString(), anyString(), anyString()); anyLong(), anyString(), anyString(), anyString());
} }
@ -256,12 +239,29 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
} }
@Test @Test
public void testValidateMail_notExists() { public void testSendSingleMail_noValidEmail() {
// 准备参数 // 准备参数
// mock 方法 Long userId = randomLongId();
String templateCode = RandomUtils.randomString();
Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234")
.put("op", "login").build();
Collection<String> toMails = Lists.newArrayList("invalid-email"); // 非法邮箱
// mock MailTemplateService 的方法
MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> {
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setContent("验证码为{code}, 操作为{op}");
o.setParams(Lists.newArrayList("code", "op"));
});
when(mailTemplateService.getMailTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);
// mock MailAccountService 的方法
MailAccountDO account = randomPojo(MailAccountDO.class);
when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);
// 调用,并断言异常 // 调用,并断言异常
assertServiceException(() -> mailSendService.validateMail(null), assertServiceException(() -> mailSendService.sendSingleMail(toMails, null, null, userId,
UserTypeEnum.ADMIN.getValue(), templateCode, templateParams),
MAIL_SEND_MAIL_NOT_EXISTS); MAIL_SEND_MAIL_NOT_EXISTS);
} }
@ -287,7 +287,8 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
assertEquals(account.getPort(), mailAccount.getPort()); assertEquals(account.getPort(), mailAccount.getPort());
assertEquals(account.getSslEnable(), mailAccount.isSslEnable()); assertEquals(account.getSslEnable(), mailAccount.isSslEnable());
return true; return true;
}), eq(message.getMail()), eq(message.getTitle()), eq(message.getContent()), eq(true))) }), eq(message.getToMails()), eq(message.getCcMails()), eq(message.getBccMails()),
eq(message.getTitle()), eq(message.getContent()), eq(true)))
.thenReturn(messageId); .thenReturn(messageId);
// 调用 // 调用
@ -310,15 +311,16 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
// mock 方法(发送邮件) // mock 方法(发送邮件)
Exception e = new NullPointerException("啦啦啦"); Exception e = new NullPointerException("啦啦啦");
mailUtilMock.when(() -> MailUtil.send(argThat(mailAccount -> { mailUtilMock.when(() -> MailUtil.send(argThat(mailAccount -> {
assertEquals("芋艿 <7685@qq.com>", mailAccount.getFrom()); assertEquals("芋艿 <7685@qq.com>", mailAccount.getFrom());
assertTrue(mailAccount.isAuth()); assertTrue(mailAccount.isAuth());
assertEquals(account.getUsername(), mailAccount.getUser()); assertEquals(account.getUsername(), mailAccount.getUser());
assertArrayEquals(account.getPassword().toCharArray(), mailAccount.getPass()); assertArrayEquals(account.getPassword().toCharArray(), mailAccount.getPass());
assertEquals(account.getHost(), mailAccount.getHost()); assertEquals(account.getHost(), mailAccount.getHost());
assertEquals(account.getPort(), mailAccount.getPort()); assertEquals(account.getPort(), mailAccount.getPort());
assertEquals(account.getSslEnable(), mailAccount.isSslEnable()); assertEquals(account.getSslEnable(), mailAccount.isSslEnable());
return true; return true;
}), eq(message.getMail()), eq(message.getTitle()), eq(message.getContent()), eq(true))).thenThrow(e); }), eq(message.getToMails()), eq(message.getCcMails()), eq(message.getBccMails()),
eq(message.getTitle()), eq(message.getContent()), eq(true))).thenThrow(e);
// 调用 // 调用
mailSendService.doSendMail(message); mailSendService.doSendMail(message);

View File

@ -553,7 +553,9 @@ CREATE TABLE IF NOT EXISTS "system_mail_log" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint, "user_id" bigint,
"user_type" varchar, "user_type" varchar,
"to_mail" varchar NOT NULL, "to_mails" varchar NOT NULL,
"cc_mails" varchar,
"bcc_mails" varchar,
"account_id" bigint NOT NULL, "account_id" bigint NOT NULL,
"from_mail" varchar NOT NULL, "from_mail" varchar NOT NULL,
"template_id" bigint NOT NULL, "template_id" bigint NOT NULL,