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;
+ }
+
/**
* 处理排它网关
*
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
index 32be076807..00d30adf9c 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
@@ -250,6 +250,14 @@ public interface BpmTaskService {
*/
void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO);
+ /**
+ * 撤回任务
+ *
+ * @param userId 用户编号
+ * @param taskId 任务编号
+ */
+ void withdrawTask(Long userId, String taskId);
+
// ========== Event 事件相关方法 ==========
/**
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
index bae0e3f714..52e0f69084 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -196,7 +196,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
/**
* 获得用户指定 processInstanceId 流程编号下的首个“待办”(未审批、且可审核)的任务
*
- * @param userId 用户编号
+ * @param userId 用户编号
* @param processInstanceId 流程编号
* @return 任务
*/
@@ -599,15 +599,15 @@ public class BpmTaskServiceImpl implements BpmTaskService {
/**
* 校验选择的下一个节点的审批人,是否合法
- *
+ *
* 1. 是否有漏选:没有选择审批人
* 2. 是否有多选:非下一个节点
*
* @param taskDefinitionKey 当前任务节点标识
- * @param variables 流程变量
- * @param bpmnModel 流程模型
- * @param nextAssignees 下一个节点审批人集合(参数)
- * @param processInstance 流程实例
+ * @param variables 流程变量
+ * @param bpmnModel 流程模型
+ * @param nextAssignees 下一个节点审批人集合(参数)
+ * @param processInstance 流程实例
*/
@SuppressWarnings("unchecked")
private Map validateAndSetNextAssignees(String taskDefinitionKey, Map variables, BpmnModel bpmnModel,
@@ -659,7 +659,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
approveUserSelectAssignees = new HashMap<>();
}
approveUserSelectAssignees.put(nextFlowNode.getId(), assignees);
- Map> existingApproveUserSelectAssignees = (Map>) variables.get(
+ Map> existingApproveUserSelectAssignees = (Map>) variables.get(
BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES);
if (CollUtil.isNotEmpty(existingApproveUserSelectAssignees)) {
approveUserSelectAssignees.putAll(existingApproveUserSelectAssignees);
@@ -1177,6 +1177,63 @@ public class BpmTaskServiceImpl implements BpmTaskService {
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 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 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 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. 任务前置通知
- if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){
+ if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())) {
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting();
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
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())
.finished();
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())
.setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName()));
return;
@@ -1362,7 +1419,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
return;
}
List sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingFlows( // 获取所有上一个节点
- BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())),
+ BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())),
SequenceFlow::getSourceRef);
if (sameAssigneeQuery.taskDefinitionKeys(sourceTaskIds).count() > 0) {
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));
if (userTaskElement.getId().equals(START_USER_NODE_ID)
&& (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核
- || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
+ || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
&& ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
.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();
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
index aeeed316dd..30d51cf472 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
@@ -14,7 +14,7 @@ public interface LogRecordConstants {
String CRM_CLUE_CREATE_SUB_TYPE = "创建线索";
String CRM_CLUE_CREATE_SUCCESS = "创建了线索{{#clue.name}}";
String CRM_CLUE_UPDATE_SUB_TYPE = "更新线索";
- String CRM_CLUE_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}";
+ String CRM_CLUE_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReqVO}}";
String CRM_CLUE_DELETE_SUB_TYPE = "删除线索";
String CRM_CLUE_DELETE_SUCCESS = "删除了线索【{{#clueName}}】";
String CRM_CLUE_TRANSFER_SUB_TYPE = "转移线索";
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
index d45635c116..224764c08d 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
@@ -106,7 +106,7 @@ public class CrmClueServiceImpl implements CrmClueService {
// 3. 记录操作日志上下文
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());
}
diff --git a/yudao-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm
index e1305586cc..3f290ccff8 100644
--- a/yudao-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm
+++ b/yudao-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm
@@ -170,6 +170,7 @@
await this.#[[$modal]]#.confirm('是否确认删除?')
try {
await ${simpleClassName}Api.delete${subSimpleClassName}List(this.checkedIds);
+ this.checkedIds = [];
await this.getList();
this.#[[$modal]]#.msgSuccess("删除成功");
} catch {}
diff --git a/yudao-module-infra/src/main/resources/codegen/vue/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue/views/index.vue.vm
index 30014a8ff4..bbc913114a 100644
--- a/yudao-module-infra/src/main/resources/codegen/vue/views/index.vue.vm
+++ b/yudao-module-infra/src/main/resources/codegen/vue/views/index.vue.vm
@@ -338,6 +338,7 @@ export default {
await this.#[[$modal]]#.confirm('是否确认删除?')
try {
await ${simpleClassName}Api.delete${simpleClassName}List(this.checkedIds);
+ this.checkedIds = [];
await this.getList();
this.#[[$modal]]#.msgSuccess("删除成功");
} catch {}
diff --git a/yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm
index f9fbb9787b..a94cab5a59 100644
--- a/yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm
+++ b/yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm
@@ -209,6 +209,7 @@ const handleDeleteBatch = async () => {
// 删除的二次确认
await message.delConfirm()
await ${simpleClassName}Api.delete${subSimpleClassName}List(checkedIds.value);
+ checkedIds.value = [];
message.success(t('common.delSuccess'))
await getList();
} catch {}
diff --git a/yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm
index 851bc2b5e4..dfb97804ce 100644
--- a/yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm
+++ b/yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm
@@ -366,6 +366,7 @@ const handleDeleteBatch = async () => {
// 删除的二次确认
await message.delConfirm()
await ${simpleClassName}Api.delete${simpleClassName}List(checkedIds.value);
+ checkedIds.value = [];
message.success(t('common.delSuccess'))
await getList();
} catch {}
diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm
index bb743305f8..6553ed0c87 100644
--- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm
+++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm
@@ -168,6 +168,7 @@ async function handleDeleteBatch() {
});
try {
await delete${simpleClassName}List(checkedIds.value);
+ checkedIds.value = [];
message.success( $t('ui.actionMessage.deleteSuccess') );
await getList();
} finally {
diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm
index cfd85589c9..999257d91d 100644
--- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm
+++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm
@@ -92,6 +92,7 @@ async function handleDeleteBatch() {
});
try {
await delete${subSimpleClassName}List(checkedIds.value);
+ checkedIds.value = [];
message.success( $t('ui.actionMessage.deleteSuccess') );
await getList();
} finally {
diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm
index 635e12ac24..1e13de2e97 100644
--- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm
+++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm
@@ -102,6 +102,7 @@ async function handleDeleteBatch() {
});
try {
await delete${simpleClassName}List(checkedIds.value);
+ checkedIds.value = [];
message.success({
content: $t('ui.actionMessage.deleteSuccess'),
key: 'action_key_msg',
diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm
index 4001ed3992..e046226efc 100644
--- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm
+++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm
@@ -82,6 +82,7 @@ async function handleDeleteBatch() {
});
try {
await delete${subSimpleClassName}List(checkedIds.value);
+ checkedIds.value = [];
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
key: 'action_key_msg',
diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/index.vue.vm
index 9897ba6773..ae77cd4c7b 100644
--- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/index.vue.vm
+++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/index.vue.vm
@@ -163,6 +163,7 @@ async function handleDeleteBatch() {
});
try {
await delete${simpleClassName}List(checkedIds.value);
+ checkedIds.value = [];
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
await getList();
} finally {
diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/list_sub_erp.vue.vm
index e27965e4c1..ccad79a0d9 100644
--- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/list_sub_erp.vue.vm
+++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/list_sub_erp.vue.vm
@@ -87,6 +87,7 @@ async function handleDeleteBatch() {
});
try {
await delete${subSimpleClassName}List(checkedIds.value);
+ checkedIds.value = [];
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
await getList();
} finally {
diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/index.vue.vm
index f9232d6b50..c29beb9aa9 100644
--- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/index.vue.vm
+++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/index.vue.vm
@@ -99,6 +99,7 @@ async function handleDeleteBatch() {
});
try {
await delete${simpleClassName}List(checkedIds.value);
+ checkedIds.value = [];
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
} finally {
diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/list_sub_erp.vue.vm
index 5afb9c7a0d..13a2415efd 100644
--- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/list_sub_erp.vue.vm
+++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/list_sub_erp.vue.vm
@@ -79,6 +79,7 @@ async function handleDeleteBatch() {
});
try {
await delete${subSimpleClassName}List(checkedIds.value);
+ checkedIds.value = [];
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
} finally {
diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java
index 3ca643ca6a..8e7884a7a2 100644
--- a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java
+++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java
@@ -28,6 +28,9 @@ public class MpMessagePageReqVO extends PageParam {
@Schema(description = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
private String openid;
+ @Schema(description = "公众号粉丝 UserId", example = "1")
+ private String userId;
+
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java
index 72ba566277..0f11bdfe55 100644
--- a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java
+++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java
@@ -15,6 +15,7 @@ public interface MpMessageMapper extends BaseMapperX {
.eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId())
.eqIfPresent(MpMessageDO::getType, reqVO.getType())
.eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenid())
+ .eqIfPresent(MpMessageDO::getUserId, reqVO.getUserId())
.betweenIfPresent(MpMessageDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(MpMessageDO::getId));
}
diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
index fee7cddba8..caf210fa66 100644
--- a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
+++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
@@ -353,7 +353,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient toMails;
+ /**
+ * 抄送邮箱
+ */
+ private List<@Email String> ccMails;
+ /**
+ * 密送邮箱
+ */
+ private List<@Email String> bccMails;
+
/**
* 邮件模板编号
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http
index f42dfcd030..52a724bf35 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http
@@ -11,6 +11,24 @@ tag: Yunai.local
"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 接口 => 成功(无验证码)
POST {{baseUrl}}/system/auth/login
Content-Type: application/json
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/DeptController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/DeptController.java
index 86cdeed1d8..e4a5c1d9c2 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/DeptController.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/DeptController.java
@@ -56,6 +56,15 @@ public class DeptController {
return success(true);
}
+ @DeleteMapping("/delete-list")
+ @Operation(summary = "批量删除部门")
+ @Parameter(name = "ids", description = "编号列表", required = true)
+ @PreAuthorize("@ss.hasPermission('system:dept:delete')")
+ public CommonResult deleteDeptList(@RequestParam("ids") List ids) {
+ deptService.deleteDeptList(ids);
+ return success(true);
+ }
+
@GetMapping("/list")
@Operation(summary = "获取部门列表")
@PreAuthorize("@ss.hasPermission('system:dept:query')")
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java
index eb888999d3..1018da9784 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java
@@ -91,7 +91,8 @@ public class MailTemplateController {
@Operation(summary = "发送短信")
@PreAuthorize("@ss.hasPermission('system:mail-template:send-mail')")
public CommonResult 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()));
}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java
index 49edfffefb..8e67d1df73 100755
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java
@@ -2,13 +2,11 @@ package cn.iocoder.yudao.module.system.controller.admin.mail.vo.log;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
-import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
+import java.util.List;
import java.util.Map;
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
@Schema(description = "管理后台 - 邮件日志 Response VO")
@Data
public class MailLogRespVO {
@@ -22,8 +20,14 @@ public class MailLogRespVO {
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", example = "2")
private Byte userType;
- @Schema(description = "接收邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "76854@qq.com")
- private String toMail;
+ @Schema(description = "接收邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user1@example.com, user2@example.com")
+ private List toMails;
+
+ @Schema(description = "抄送邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user3@example.com, user4@example.com")
+ private List ccMails;
+
+ @Schema(description = "密送邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user5@example.com, user6@example.com")
+ private List bccMails;
@Schema(description = "邮箱账号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18107")
private Long accountId;
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java
index 8dd0887ede..f125d77e9a 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java
@@ -3,17 +3,24 @@ package cn.iocoder.yudao.module.system.controller.admin.mail.vo.template;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - 邮件发送 Req VO")
@Data
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 = "接收邮箱不能为空")
- private String mail;
+ private List toMails;
+
+ @Schema(description = "抄送邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "[user3@example.com, user4@example.com]")
+ private List ccMails;
+
+ @Schema(description = "密送邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "[user5@example.com, user6@example.com]")
+ private List bccMails;
@Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01")
@NotNull(message = "模板编码不能为空")
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java
index 2a0da51728..756dba2ad7 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java
@@ -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.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.module.system.enums.mail.MailSendStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -12,6 +13,7 @@ import lombok.*;
import java.io.Serializable;
import java.time.LocalDateTime;
+import java.util.List;
import java.util.Map;
/**
@@ -47,10 +49,22 @@ public class MailLogDO extends BaseDO implements Serializable {
* 枚举 {@link UserTypeEnum}
*/
private Integer userType;
+
/**
* 接收邮箱地址
*/
- private String toMail;
+ @TableField(typeHandler = StringListTypeHandler.class)
+ private List toMails;
+ /**
+ * 接收邮箱地址
+ */
+ @TableField(typeHandler = StringListTypeHandler.class)
+ private List ccMails;
+ /**
+ * 密送邮箱地址
+ */
+ @TableField(typeHandler = StringListTypeHandler.class)
+ private List bccMails;
/**
* 邮箱账号编号
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java
index 6b147cff62..44fab07a0d 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java
@@ -1,8 +1,10 @@
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.mybatis.core.mapper.BaseMapperX;
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.dal.dataobject.mail.MailLogDO;
import org.apache.ibatis.annotations.Mapper;
@@ -14,11 +16,12 @@ public interface MailLogMapper extends BaseMapperX {
return selectPage(reqVO, new LambdaQueryWrapperX()
.eqIfPresent(MailLogDO::getUserId, reqVO.getUserId())
.eqIfPresent(MailLogDO::getUserType, reqVO.getUserType())
- .likeIfPresent(MailLogDO::getToMail, reqVO.getToMail())
.eqIfPresent(MailLogDO::getAccountId, reqVO.getAccountId())
.eqIfPresent(MailLogDO::getTemplateId, reqVO.getTemplateId())
.eqIfPresent(MailLogDO::getSendStatus, reqVO.getSendStatus())
.betweenIfPresent(MailLogDO::getSendTime, reqVO.getSendTime())
+ .apply(StrUtil.isNotBlank(reqVO.getToMail()),
+ MyBatisUtils.findInSet("to_mails", reqVO.getToMail()))
.orderByDesc(MailLogDO::getId));
}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java
index 3dd12491a9..d4ed95d9b2 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java
@@ -136,15 +136,20 @@ public class AliyunSmsClient extends AbstractSmsClient {
.map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue())))
.collect(Collectors.joining("&"));
- // 2.1 请求 Header
+ // 2. 请求 Body
+ String requestBody = ""; // 短信 API 为 RPC 接口,query parameters 在 uri 中拼接,因此 request body 如果没有特殊要求,设置为空
+ String hashedRequestPayload = DigestUtil.sha256Hex(requestBody);
+
+ // 3.1 请求 Header
TreeMap headers = new TreeMap<>();
headers.put("host", HOST);
headers.put("x-acs-version", VERSION);
headers.put("x-acs-action", apiName);
headers.put("x-acs-date", FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("GMT")).format(new Date()));
headers.put("x-acs-signature-nonce", IdUtil.randomUUID());
+ headers.put("x-acs-content-sha256", hashedRequestPayload);
- // 2.2 构建签名 Header
+ // 3.2 构建签名 Header
StringBuilder canonicalHeaders = new StringBuilder(); // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起
StringBuilder signedHeadersBuilder = new StringBuilder(); // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔
headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-")
@@ -157,13 +162,13 @@ public class AliyunSmsClient extends AbstractSmsClient {
});
String signedHeaders = signedHeadersBuilder.substring(0, signedHeadersBuilder.length() - 1);
- // 3. 请求 Body
- String requestBody = ""; // 短信 API 为 RPC 接口,query parameters 在 uri 中拼接,因此 request body 如果没有特殊要求,设置为空。
- String hashedRequestBody = DigestUtil.sha256Hex(requestBody);
-
// 4. 构建 Authorization 签名
- String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n"
- + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody;
+ String canonicalRequest = "POST" + "\n" +
+ "/" + "\n" +
+ queryString + "\n" +
+ canonicalHeaders + "\n" +
+ signedHeaders + "\n" +
+ hashedRequestPayload;
String hashedCanonicalRequest = DigestUtil.sha256Hex(canonicalRequest);
String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest;
String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名
@@ -184,7 +189,7 @@ public class AliyunSmsClient extends AbstractSmsClient {
@SneakyThrows
private static String percentCode(String str) {
Assert.notNull(str, "str 不能为空");
- return HttpUtils.encodeUtf8(str)
+ return URLEncoder.encode(str, StandardCharsets.UTF_8.name())
.replace("+", "%20") // 加号 "+" 被替换为 "%20"
.replace("*", "%2A") // 星号 "*" 被替换为 "%2A"
.replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~"
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java
index 943d69b096..fe4e5dd074 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java
@@ -5,6 +5,9 @@ import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
+import java.util.Collection;
+import java.util.List;
+
/**
* 邮箱发送消息
*
@@ -21,8 +24,16 @@ public class MailSendMessage {
/**
* 接收邮件地址
*/
- @NotNull(message = "接收邮件地址不能为空")
- private String mail;
+ @NotEmpty(message = "接收邮件地址不能为空")
+ private Collection toMails;
+ /**
+ * 抄送邮件地址
+ */
+ private Collection ccMails;
+ /**
+ * 密送邮件地址
+ */
+ private Collection bccMails;
/**
* 邮件账号编号
*/
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java
index 5894332cb6..24f849a9e9 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java
@@ -7,6 +7,11 @@ import org.springframework.stereotype.Component;
import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+
/**
* Mail 邮件相关消息的 Producer
*
@@ -24,17 +29,22 @@ public class MailProducer {
* 发送 {@link MailSendMessage} 消息
*
* @param sendLogId 发送日志编码
- * @param mail 接收邮件地址
+ * @param toMails 接收邮件地址
+ * @param ccMails 抄送邮件地址
+ * @param bccMails 密送邮件地址
* @param accountId 邮件账号编号
- * @param nickname 邮件发件人
- * @param title 邮件标题
- * @param content 邮件内容
+ * @param nickname 邮件发件人
+ * @param title 邮件标题
+ * @param content 邮件内容
*/
- public void sendMailSendMessage(Long sendLogId, String mail, Long accountId,
- String nickname, String title, String content) {
+ public void sendMailSendMessage(Long sendLogId,
+ Collection toMails, Collection ccMails, Collection bccMails,
+ Long accountId, String nickname, String title, String content) {
MailSendMessage message = new MailSendMessage()
- .setLogId(sendLogId).setMail(mail).setAccountId(accountId)
- .setNickname(nickname).setTitle(title).setContent(content);
+ .setLogId(sendLogId)
+ .setToMails(toMails).setCcMails(ccMails).setBccMails(bccMails)
+ .setAccountId(accountId).setNickname(nickname)
+ .setTitle(title).setContent(content);
applicationContext.publishEvent(message);
}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java
index a0b765e590..06a688e606 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java
@@ -36,6 +36,13 @@ public interface DeptService {
*/
void deleteDept(Long id);
+ /**
+ * 批量删除部门
+ *
+ * @param ids 部门编号数组
+ */
+ void deleteDeptList(List ids);
+
/**
* 获得部门信息
*
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
index 2cad950470..05d4dbab10 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
@@ -88,6 +88,21 @@ public class DeptServiceImpl implements DeptService {
deptMapper.deleteById(id);
}
+ @Override
+ @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
+ allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
+ public void deleteDeptList(List ids) {
+ // 校验是否有子部门
+ for (Long id : ids) {
+ if (deptMapper.selectCountByParentId(id) > 0) {
+ throw exception(DEPT_EXITS_CHILDREN);
+ }
+ }
+
+ // 批量删除部门
+ deptMapper.deleteByIds(ids);
+ }
+
@VisibleForTesting
void validateDeptExists(Long id) {
if (id == null) {
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java
index 4a0b204385..1c66e55ef4 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java
@@ -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.MailTemplateDO;
+import java.util.Collection;
+import java.util.List;
import java.util.Map;
/**
@@ -35,18 +37,21 @@ public interface MailLogService {
/**
* 创建邮件日志
*
- * @param userId 用户编码
- * @param userType 用户类型
- * @param toMail 收件人邮件
- * @param account 邮件账号信息
- * @param template 模版信息
+ * @param userId 用户编码
+ * @param userType 用户类型
+ * @param toMails 收件人邮件
+ * @param ccMails 收件人邮件
+ * @param bccMails 收件人邮件
+ * @param account 邮件账号信息
+ * @param template 模版信息
* @param templateContent 模版内容
- * @param templateParams 模版参数
- * @param isSend 是否发送成功
+ * @param templateParams 模版参数
+ * @param isSend 是否发送成功
* @return 日志编号
*/
- Long createMailLog(Long userId, Integer userType, String toMail,
- MailAccountDO account, MailTemplateDO template ,
+ Long createMailLog(Long userId, Integer userType,
+ Collection toMails, Collection ccMails, Collection bccMails,
+ MailAccountDO account, MailTemplateDO template,
String templateContent, Map templateParams, Boolean isSend);
/**
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java
index 292acab072..cde3dbac83 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java
@@ -1,5 +1,6 @@
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.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
@@ -12,8 +13,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage;
@@ -41,7 +41,8 @@ public class MailLogServiceImpl implements MailLogService {
}
@Override
- public Long createMailLog(Long userId, Integer userType, String toMail,
+ public Long createMailLog(Long userId, Integer userType,
+ Collection toMails, Collection ccMails, Collection bccMails,
MailAccountDO account, MailTemplateDO template,
String templateContent, Map templateParams, Boolean isSend) {
MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder();
@@ -49,7 +50,8 @@ public class MailLogServiceImpl implements MailLogService {
logDOBuilder.sendStatus(Objects.equals(isSend, true) ? MailSendStatusEnum.INIT.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())
// 模板相关字段
.templateId(template.getId()).templateCode(template.getCode()).templateNickname(template.getNickname())
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java
index 898816868f..1b600bc90c 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java
@@ -1,7 +1,9 @@
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 java.util.Collection;
import java.util.Map;
/**
@@ -15,38 +17,53 @@ public interface MailSendService {
/**
* 发送单条邮件给管理后台的用户
*
- * @param mail 邮箱
* @param userId 用户编码
+ * @param toMails 收件邮箱
+ * @param ccMails 抄送邮箱
+ * @param bccMails 密送邮箱
* @param templateCode 邮件模版编码
* @param templateParams 邮件模版参数
* @return 发送日志编号
*/
- Long sendSingleMailToAdmin(String mail, Long userId,
- String templateCode, Map templateParams);
+ default Long sendSingleMailToAdmin(Long userId,
+ Collection toMails, Collection ccMails, Collection bccMails,
+ String templateCode, Map templateParams) {
+ return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.ADMIN.getValue(),
+ templateCode, templateParams);
+ }
/**
* 发送单条邮件给用户 APP 的用户
*
- * @param mail 邮箱
* @param userId 用户编码
+ * @param toMails 收件邮箱
+ * @param ccMails 抄送邮箱
+ * @param bccMails 密送邮箱
* @param templateCode 邮件模版编码
* @param templateParams 邮件模版参数
* @return 发送日志编号
*/
- Long sendSingleMailToMember(String mail, Long userId,
- String templateCode, Map templateParams);
+ default Long sendSingleMailToMember(Long userId,
+ Collection toMails, Collection ccMails, Collection bccMails,
+ String templateCode, Map templateParams) {
+ return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.MEMBER.getValue(),
+ templateCode, templateParams);
+ }
/**
- * 发送单条邮件给用户
+ * 发送单条邮件
*
- * @param mail 邮箱
- * @param userId 用户编码
+ * @param toMails 收件邮箱
+ * @param ccMails 抄送邮箱
+ * @param bccMails 密送邮箱
+ * @param userId 用户编号
* @param userType 用户类型
* @param templateCode 邮件模版编码
* @param templateParams 邮件模版参数
* @return 发送日志编号
*/
- Long sendSingleMail(String mail, Long userId, Integer userType,
+ Long sendSingleMail(Collection toMails, Collection ccMails, Collection bccMails,
+ Long userId, Integer userType,
String templateCode, Map templateParams);
/**
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java
index ea139cfa84..682696f932 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java
@@ -1,8 +1,8 @@
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.extra.mail.MailAccount;
-import cn.hutool.extra.mail.MailUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
@@ -13,11 +13,15 @@ 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.user.AdminUserService;
import com.google.common.annotations.VisibleForTesting;
+import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
+import org.dromara.hutool.extra.mail.MailAccount;
+import org.dromara.hutool.extra.mail.MailUtil;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.LinkedHashSet;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -50,56 +54,67 @@ public class MailSendServiceImpl implements MailSendService {
private MailProducer mailProducer;
@Override
- public Long sendSingleMailToAdmin(String mail, Long userId,
- String templateCode, Map templateParams) {
- // 如果 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 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,
+ public Long sendSingleMail(Collection toMails, Collection ccMails, Collection bccMails,
+ Long userId, Integer userType,
String templateCode, Map templateParams) {
- // 校验邮箱模版是否合法
+ // 1.1 校验邮箱模版是否合法
MailTemplateDO template = validateMailTemplate(templateCode);
- // 校验邮箱账号是否合法
+ // 1.2 校验邮箱账号是否合法
MailAccountDO account = validateMailAccount(template.getAccountId());
-
- // 校验邮箱是否存在
- mail = validateMail(mail);
+ // 1.3 校验邮件参数是否缺失
validateTemplateParams(template, templateParams);
+ // 2. 组装邮箱
+ String userMail = getUserMail(userId, userType);
+ Collection toMailSet = new LinkedHashSet<>();
+ Collection ccMailSet = new LinkedHashSet<>();
+ Collection 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());
String title = mailTemplateService.formatMailTemplateContent(template.getTitle(), 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);
// 发送 MQ 消息,异步执行发送短信
if (isSend) {
- mailProducer.sendMailSendMessage(sendLogId, mail, account.getId(),
- template.getNickname(), title, content);
+ mailProducer.sendMailSendMessage(sendLogId, toMailSet, ccMailSet, bccMailSet,
+ account.getId(), template.getNickname(), title, content);
}
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
public void doSendMail(MailSendMessage message) {
// 1. 创建发送账号
@@ -107,7 +122,7 @@ public class MailSendServiceImpl implements MailSendService {
MailAccount mailAccount = buildMailAccount(account, message.getNickname());
// 2. 发送邮件
try {
- String messageId = MailUtil.send(mailAccount, message.getMail(),
+ String messageId = MailUtil.send(mailAccount, message.getToMails(), message.getCcMails(), message.getBccMails(),
message.getTitle(), message.getContent(), true);
// 3. 更新结果(成功)
mailLogService.updateMailSendResult(message.getLogId(), messageId, null);
@@ -120,7 +135,7 @@ public class MailSendServiceImpl implements MailSendService {
private MailAccount buildMailAccount(MailAccountDO account, String nickname) {
String from = StrUtil.isNotEmpty(nickname) ? nickname + " <" + account.getMail() + ">" : account.getMail();
return new MailAccount().setFrom(from).setAuth(true)
- .setUser(account.getUsername()).setPass(account.getPassword())
+ .setUser(account.getUsername()).setPass(account.getPassword().toCharArray())
.setHost(account.getHost()).setPort(account.getPort())
.setSslEnable(account.getSslEnable()).setStarttlsEnable(account.getStarttlsEnable());
}
@@ -147,16 +162,8 @@ public class MailSendServiceImpl implements MailSendService {
return account;
}
- @VisibleForTesting
- String validateMail(String mail) {
- if (StrUtil.isEmpty(mail)) {
- throw exception(MAIL_SEND_MAIL_NOT_EXISTS);
- }
- return mail;
- }
-
/**
- * 校验邮件参数是否确实
+ * 校验邮件参数是否缺失
*
* @param template 邮箱模板
* @param templateParams 参数列表
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
index d9d0ea2d41..bdc461639b 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
@@ -255,9 +255,6 @@ public class MenuServiceImpl implements MenuService {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的菜单
- if (id == null) {
- throw exception(MENU_NAME_DUPLICATE);
- }
if (!menu.getId().equals(id)) {
throw exception(MENU_NAME_DUPLICATE);
}
diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java
index abfa225daf..000144aa6f 100644
--- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java
+++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java
@@ -105,12 +105,40 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
}
@Test
- public void testValidateDeptExists_notFound() {
+ public void testDeleteDeptList_success() {
+ // mock 数据
+ DeptDO deptDO1 = randomPojo(DeptDO.class);
+ deptMapper.insert(deptDO1);
+ DeptDO deptDO2 = randomPojo(DeptDO.class);
+ deptMapper.insert(deptDO2);
// 准备参数
- Long id = randomLongId();
+ List ids = Arrays.asList(deptDO1.getId(), deptDO2.getId());
+
+ // 调用
+ deptService.deleteDeptList(ids);
+ // 校验数据不存在了
+ assertNull(deptMapper.selectById(deptDO1.getId()));
+ assertNull(deptMapper.selectById(deptDO2.getId()));
+ }
+
+ @Test
+ public void testDeleteDeptList_exitsChildren() {
+ // mock 数据
+ DeptDO parentDept = randomPojo(DeptDO.class);
+ deptMapper.insert(parentDept);
+ DeptDO childrenDeptDO = randomPojo(DeptDO.class, o -> {
+ o.setParentId(parentDept.getId());
+ o.setStatus(randomCommonStatus());
+ });
+ deptMapper.insert(childrenDeptDO);
+ DeptDO anotherDept = randomPojo(DeptDO.class);
+ deptMapper.insert(anotherDept);
+
+ // 准备参数(包含有子部门的 parentDept)
+ List ids = Arrays.asList(parentDept.getId(), anotherDept.getId());
// 调用, 并断言异常
- assertServiceException(() -> deptService.validateDeptExists(id), DEPT_NOT_FOUND);
+ assertServiceException(() -> deptService.deleteDeptList(ids), DEPT_EXITS_CHILDREN);
}
@Test
diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java
index 152c6d8627..ec8aadacd9 100755
--- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java
+++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java
@@ -10,10 +10,12 @@ 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.mysql.mail.MailLogMapper;
import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum;
+import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
-import javax.annotation.Resource;
+import jakarta.annotation.Resource;
+import java.util.Collection;
import java.util.Map;
import static cn.hutool.core.util.RandomUtil.randomEle;
@@ -43,7 +45,9 @@ public class MailLogServiceImplTest extends BaseDbUnitTest {
// 准备参数
Long userId = randomLongId();
Integer userType = randomEle(UserTypeEnum.values()).getValue();
- String toMail = randomEmail();
+ Collection toMails = Lists.newArrayList(randomEmail(), randomEmail());
+ Collection ccMails = Lists.newArrayList(randomEmail());
+ Collection bccMails = Lists.newArrayList(randomEmail());
MailAccountDO account = randomPojo(MailAccountDO.class);
MailTemplateDO template = randomPojo(MailTemplateDO.class);
String templateContent = randomString();
@@ -52,14 +56,20 @@ public class MailLogServiceImplTest extends BaseDbUnitTest {
// 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);
assertNotNull(log);
assertEquals(MailSendStatusEnum.INIT.getStatus(), log.getSendStatus());
assertEquals(userId, log.getUserId());
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.getMail(), log.getFromMail());
assertEquals(template.getId(), log.getTemplateId());
@@ -136,7 +146,9 @@ public class MailLogServiceImplTest extends BaseDbUnitTest {
MailLogDO dbMailLog = randomPojo(MailLogDO.class, o -> { // 等会查询到
o.setUserId(1L);
o.setUserType(UserTypeEnum.ADMIN.getValue());
- o.setToMail("768@qq.com");
+ o.setToMails(Lists.newArrayList("768@qq.com"));
+ o.setCcMails(Lists.newArrayList());
+ o.setBccMails(Lists.newArrayList());
o.setAccountId(10L);
o.setTemplateId(100L);
o.setSendStatus(MailSendStatusEnum.INIT.getStatus());
@@ -148,8 +160,8 @@ public class MailLogServiceImplTest extends BaseDbUnitTest {
mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserId(2L)));
// 测试 userType 不匹配
mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));
- // 测试 toMail 不匹配
- mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setToMail("788@.qq.com")));
+ // 测试 toMails 不匹配(特殊:find_in_set 无法单测)
+// mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setToMails(Lists.newArrayList("788@qq.com"))));
// 测试 accountId 不匹配
mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setAccountId(11L)));
// 测试 templateId 不匹配
@@ -162,7 +174,7 @@ public class MailLogServiceImplTest extends BaseDbUnitTest {
MailLogPageReqVO reqVO = new MailLogPageReqVO();
reqVO.setUserId(1L);
reqVO.setUserType(UserTypeEnum.ADMIN.getValue());
- reqVO.setToMail("768");
+// reqVO.setToMail("768@qq.com");
reqVO.setAccountId(10L);
reqVO.setTemplateId(100L);
reqVO.setSendStatus(MailSendStatusEnum.INIT.getStatus());
diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java
index b87315276d..4dd61ed453 100644
--- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java
+++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java
@@ -20,6 +20,7 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@@ -65,14 +66,18 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
}
@Test
- public void testSendSingleMailToAdmin() {
+ public void testSendSingleMail_success() {
// 准备参数
Long userId = randomLongId();
String templateCode = RandomUtils.randomString();
Map templateParams = MapUtil.builder().put("code", "1234")
.put("op", "login").build();
+ Collection toMails = Lists.newArrayList("admin@test.com");
+ Collection ccMails = Lists.newArrayList("cc@test.com");
+ Collection bccMails = Lists.newArrayList("bcc@test.com");
+
// 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);
// mock MailTemplateService 的方法
@@ -93,61 +98,27 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);
// mock MailLogService 的方法
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);
// 调用
- Long resultMailLogId = mailSendService.sendSingleMailToAdmin(null, userId, templateCode, templateParams);
+ Long resultMailLogId = mailSendService.sendSingleMail(toMails, ccMails, bccMails, userId,
+ UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);
// 断言
assertEquals(mailLogId, resultMailLogId);
// 断言调用
- verify(mailProducer).sendMailSendMessage(eq(mailLogId), eq(user.getEmail()),
- eq(account.getId()), eq(template.getNickname()), eq(title), eq(content));
- }
-
- @Test
- public void testSendSingleMailToMember() {
- // 准备参数
- Long userId = randomLongId();
- String templateCode = RandomUtils.randomString();
- Map templateParams = MapUtil.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),
+ verify(mailProducer).sendMailSendMessage(eq(mailLogId),
+ 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.getId()), eq(template.getNickname()), eq(title), eq(content));
}
/**
- * 发送成功,当短信模板开启时
+ * 发送成功,当邮件模板开启时
*/
@Test
public void testSendSingleMail_successWhenMailTemplateEnable() {
@@ -158,6 +129,8 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
String templateCode = RandomUtils.randomString();
Map templateParams = MapUtil.builder().put("code", "1234")
.put("op", "login").build();
+ Collection toMails = Lists.newArrayList(mail);
+
// mock MailTemplateService 的方法
MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> {
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
@@ -176,23 +149,29 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);
// mock MailLogService 的方法
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);
// 调用
- Long resultMailLogId = mailSendService.sendSingleMail(mail, userId, userType, templateCode, templateParams);
+ Long resultMailLogId = mailSendService.sendSingleMail(toMails, null, null, userId, userType, templateCode, templateParams);
// 断言
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));
}
/**
- * 发送成功,当短信模板关闭时
+ * 发送成功,当邮件模板关闭时
*/
@Test
- public void testSendSingleMail_successWhenSmsTemplateDisable() {
+ public void testSendSingleMail_successWhenMailTemplateDisable() {
// 准备参数
String mail = randomEmail();
Long userId = randomLongId();
@@ -200,6 +179,8 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
String templateCode = RandomUtils.randomString();
Map templateParams = MapUtil.builder().put("code", "1234")
.put("op", "login").build();
+ Collection toMails = Lists.newArrayList(mail);
+
// mock MailTemplateService 的方法
MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> {
o.setStatus(CommonStatusEnum.DISABLE.getStatus());
@@ -218,15 +199,18 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);
// mock MailLogService 的方法
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);
// 调用
- Long resultMailLogId = mailSendService.sendSingleMail(mail, userId, userType, templateCode, templateParams);
+ Long resultMailLogId = mailSendService.sendSingleMail(toMails, null, null, userId, userType, templateCode, templateParams);
// 断言
assertEquals(mailLogId, resultMailLogId);
// 断言调用
- verify(mailProducer, times(0)).sendMailSendMessage(anyLong(), anyString(),
+ verify(mailProducer, times(0)).sendMailSendMessage(anyLong(), any(), any(), any(),
anyLong(), anyString(), anyString(), anyString());
}
@@ -255,12 +239,29 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
}
@Test
- public void testValidateMail_notExists() {
+ public void testSendSingleMail_noValidEmail() {
// 准备参数
- // mock 方法
+ Long userId = randomLongId();
+ String templateCode = RandomUtils.randomString();
+ Map templateParams = MapUtil.builder().put("code", "1234")
+ .put("op", "login").build();
+ Collection 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);
}
@@ -286,7 +287,8 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
assertEquals(account.getPort(), mailAccount.getPort());
assertEquals(account.getSslEnable(), mailAccount.isSslEnable());
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);
// 调用
@@ -317,7 +319,8 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
assertEquals(account.getPort(), mailAccount.getPort());
assertEquals(account.getSslEnable(), mailAccount.isSslEnable());
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);
diff --git a/yudao-module-system/src/test/resources/sql/create_tables.sql b/yudao-module-system/src/test/resources/sql/create_tables.sql
index 4df039b8df..d8b68369aa 100644
--- a/yudao-module-system/src/test/resources/sql/create_tables.sql
+++ b/yudao-module-system/src/test/resources/sql/create_tables.sql
@@ -553,7 +553,9 @@ CREATE TABLE IF NOT EXISTS "system_mail_log" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint,
"user_type" varchar,
- "to_mail" varchar NOT NULL,
+ "to_mails" varchar NOT NULL,
+ "cc_mails" varchar,
+ "bcc_mails" varchar,
"account_id" bigint NOT NULL,
"from_mail" varchar NOT NULL,
"template_id" bigint NOT NULL,
diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml
index d82d3974a5..1decd79cda 100644
--- a/yudao-server/src/main/resources/application.yaml
+++ b/yudao-server/src/main/resources/application.yaml
@@ -245,6 +245,13 @@ yudao:
security:
permit-all_urls:
- /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录
+ api-encrypt:
+ enable: true # 是否开启 API 加密
+ algorithm: AES # 加密算法,支持 AES、RSA 等
+ request-key: 52549111389893486934626385991395 # 【AES 案例】请求加密的秘钥,,必须 16、24、32 位
+ response-key: 96103715984234343991809655248883 # 【AES 案例】响应加密的秘钥,AES 案例,必须 16、24、32 位
+# request-key: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKWzasimcZ1icsWDPVdTXcZs1DkOWjI+m9bTQU8aOqflnomkr6QO1WWeSHBHzuJGsTlV/ZY2pFfq/NstKC94hBjx7yioYJvzb2bKN/Uy4j5nM3iCF//u0RiFkkY8j0Bt/EWoFTOb6RHf8cHIAjbYYtP3pYzbpCIwryfe0g//KIuzAgMBAAECgYADDjZrYcpZjR2xr7RbXmGtzYbyUGXwZEAqa3XaWBD51J2iSyOkAlQEDjGmxGQ3vvb4qDHHadWI+3/TKNeDXJUO+xTVJrnismK5BsHyC6dfxlIK/5BAuknryTca/3UoA1yomS9ZlF3Q0wcecaDoEnSmZEaTrp9T3itPAz4KnGjv5QJBAN5mNcfu6iJ5ktNvEdzqcxkKwbXb9Nq1SLnmTvt+d5TPX7eQ9fCwtOfVu5iBLhhZzb5PJ7pSN3Zt6rl5/jPOGv0CQQC+vETX9oe1wbxZSv6/RBGy0Xow6GndbJwvd89PcAJ2h+OJXWtg/rRHB3t9EQm7iis0XbZTapj19E4U6l8EibhvAkEA1CvYpRwmHKu1SqdM+GBnW/2qHlBwwXJvpoK02TOm674HR/4w0+YRQJfkd7LOAgcyxJuJgDTNmtt0MmzS+iNoFQJAMVSUIZ77XoDq69U/qcw7H5qaFcgmiUQr6QL9tTftCyb+LGri+MUnby96OtCLSdvkbLjIDS8GvKYhA7vSM2RDNQJBAKGyVVnFFIrbK3yuwW71yvxQEGoGxlgvZSezZ4vGgqTxrr9HvAtvWLwR6rpe6ybR/x8uUtoW7NRBWgpiIFwjvY4= # 【RSA 案例】请求解密的私钥
+# response-key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDh/CHyBcS/zEfVyINVA7+c9Xxl0CPdxPMK1OIjxaLy/7BLfbkoEpI8onQtjuzfpuxCraDem9bu3BMF0pMH95HytI3Vi0kGjaV+WLIalwgc2w37oA2sbsmKzQOP7SDLO5s2QJNAD7kVwd+Q5rqaLu2MO0xVv+0IUJhn83hClC0L5wIDAQAB # 【RSA 案例】响应加密的公钥
websocket:
enable: true # websocket的开关
path: /infra/ws # 路径