mirror of
https://codeup.aliyun.com/67c68d4e484ca2f0a13ac3c1/ydc/jsowell-charger-web.git
synced 2026-07-22 23:22:32 +08:00
新增待办管理端异常处理能力
This commit is contained in:
@@ -134,6 +134,47 @@ WHERE role_info.status = '0'
|
|||||||
-- INSERT INTO sys_role_menu (role_id, menu_id) VALUES (<业务处理角色ID>, @todoCompleteMenuId);
|
-- INSERT INTO sys_role_menu (role_id, menu_id) VALUES (<业务处理角色ID>, @todoCompleteMenuId);
|
||||||
-- INSERT INTO sys_role_menu (role_id, menu_id) VALUES (<任务关闭角色ID>, @todoCancelMenuId);
|
-- INSERT INTO sys_role_menu (role_id, menu_id) VALUES (<任务关闭角色ID>, @todoCancelMenuId);
|
||||||
|
|
||||||
|
-- 管理端异常任务处理权限,仅默认授予 role_key = 'admin' 的系统管理员。
|
||||||
|
INSERT INTO sys_menu (
|
||||||
|
menu_name, parent_id, order_num, path, component, is_frame, is_cache,
|
||||||
|
menu_type, visible, status, perms, icon, create_by, create_time, remark
|
||||||
|
)
|
||||||
|
SELECT permission_info.menu_name, @todoMenuId, permission_info.order_num, '#', '', 1, 0,
|
||||||
|
'F', '0', '0', permission_info.perms, '#', 'system', sysdate(), permission_info.remark
|
||||||
|
FROM (
|
||||||
|
SELECT '待办管理查询' AS menu_name, 11 AS order_num, 'system:todo:admin:list' AS perms, '查看全量待办' AS remark
|
||||||
|
UNION ALL SELECT '待办人工创建', 12, 'system:todo:admin:create', '人工补发异常待办'
|
||||||
|
UNION ALL SELECT '待办重新分配', 13, 'system:todo:admin:assign', '重新分配有效待办'
|
||||||
|
UNION ALL SELECT '待办管理取消', 14, 'system:todo:admin:cancel', '管理员关闭异常待办'
|
||||||
|
UNION ALL SELECT '待办逻辑隐藏', 15, 'system:todo:admin:remove', '隐藏终态待办记录'
|
||||||
|
) permission_info
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM sys_menu existing_menu
|
||||||
|
WHERE existing_menu.parent_id = @todoMenuId
|
||||||
|
AND existing_menu.perms = permission_info.perms
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||||
|
SELECT role_info.role_id, menu_info.menu_id
|
||||||
|
FROM sys_role role_info
|
||||||
|
JOIN sys_menu menu_info
|
||||||
|
ON menu_info.parent_id = @todoMenuId
|
||||||
|
AND menu_info.perms IN (
|
||||||
|
'system:todo:admin:list',
|
||||||
|
'system:todo:admin:create',
|
||||||
|
'system:todo:admin:assign',
|
||||||
|
'system:todo:admin:cancel',
|
||||||
|
'system:todo:admin:remove'
|
||||||
|
)
|
||||||
|
WHERE role_info.role_key = 'admin'
|
||||||
|
AND role_info.status = '0'
|
||||||
|
AND role_info.del_flag = '0'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM sys_role_menu relation
|
||||||
|
WHERE relation.role_id = role_info.role_id
|
||||||
|
AND relation.menu_id = menu_info.menu_id
|
||||||
|
);
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
@todoMenuId AS todo_menu_id,
|
@todoMenuId AS todo_menu_id,
|
||||||
@todoQueryMenuId AS todo_query_menu_id,
|
@todoQueryMenuId AS todo_query_menu_id,
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.jsowell.web.controller.system;
|
||||||
|
|
||||||
|
import com.jsowell.common.annotation.Log;
|
||||||
|
import com.jsowell.common.core.controller.BaseController;
|
||||||
|
import com.jsowell.common.core.domain.AjaxResult;
|
||||||
|
import com.jsowell.common.core.page.TableDataInfo;
|
||||||
|
import com.jsowell.common.enums.BusinessType;
|
||||||
|
import com.jsowell.system.domain.SysTodoTask;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAdminCreateRequest;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAdminQuery;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAssignRequest;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskCancelRequest;
|
||||||
|
import com.jsowell.system.service.TodoTaskAdminService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待办管理端接口。
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/todo/admin")
|
||||||
|
public class SysTodoTaskAdminController extends BaseController {
|
||||||
|
@Autowired
|
||||||
|
private TodoTaskAdminService todoTaskAdminService;
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('system:todo:admin:list')")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo list(TodoTaskAdminQuery query) {
|
||||||
|
startPage();
|
||||||
|
List<SysTodoTask> list = todoTaskAdminService.list(query);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('system:todo:admin:create')")
|
||||||
|
@Log(title = "待办管理", businessType = BusinessType.INSERT)
|
||||||
|
@PostMapping("/create")
|
||||||
|
public AjaxResult create(@Validated @RequestBody TodoTaskAdminCreateRequest request) {
|
||||||
|
return AjaxResult.success(todoTaskAdminService.create(request, getUsername()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('system:todo:admin:assign')")
|
||||||
|
@Log(title = "待办管理", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping("/{todoId}/assign")
|
||||||
|
public AjaxResult assign(@PathVariable Long todoId,
|
||||||
|
@Validated @RequestBody TodoTaskAssignRequest request) {
|
||||||
|
return toAjax(todoTaskAdminService.assign(todoId, request, getUsername()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('system:todo:admin:cancel')")
|
||||||
|
@Log(title = "待办管理", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping("/{todoId}/cancel")
|
||||||
|
public AjaxResult cancel(@PathVariable Long todoId,
|
||||||
|
@Validated @RequestBody TodoTaskCancelRequest request) {
|
||||||
|
return toAjax(todoTaskAdminService.cancel(todoId, getUserId(), getUsername(), request.getReason()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('system:todo:admin:remove')")
|
||||||
|
@Log(title = "待办管理", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/{todoId}")
|
||||||
|
public AjaxResult hide(@PathVariable Long todoId) {
|
||||||
|
return toAjax(todoTaskAdminService.hide(todoId, getUsername()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package com.jsowell.system.service;
|
||||||
|
|
||||||
|
import com.jsowell.common.constant.Constants;
|
||||||
|
import com.jsowell.common.core.domain.entity.SysUser;
|
||||||
|
import com.jsowell.common.exception.ServiceException;
|
||||||
|
import com.jsowell.system.constant.TodoTaskConstants;
|
||||||
|
import com.jsowell.system.domain.SysTodoTask;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAdminCreateRequest;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAdminQuery;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAssignRequest;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskCreateCommand;
|
||||||
|
import com.jsowell.system.mapper.SysTodoTaskMapper;
|
||||||
|
import com.jsowell.system.mapper.SysUserMapper;
|
||||||
|
import com.jsowell.system.service.impl.TodoTaskAdminServiceImpl;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class TodoTaskAdminServiceImplTest {
|
||||||
|
@Test
|
||||||
|
void list_shouldUseAdminQueryWithoutForcingAssignee() {
|
||||||
|
Dependencies dependencies = new Dependencies();
|
||||||
|
TodoTaskAdminServiceImpl service = dependencies.createService();
|
||||||
|
TodoTaskAdminQuery query = new TodoTaskAdminQuery();
|
||||||
|
query.setTaskStatus(TodoTaskConstants.STATUS_ACTIVE);
|
||||||
|
when(dependencies.todoTaskMapper.selectAdminTodoList(query))
|
||||||
|
.thenReturn(Collections.singletonList(new SysTodoTask()));
|
||||||
|
|
||||||
|
assertEquals(1, service.list(query).size());
|
||||||
|
verify(dependencies.todoTaskMapper).selectAdminTodoList(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void create_shouldValidateUserAndGenerateIdempotentKey() {
|
||||||
|
Dependencies dependencies = new Dependencies();
|
||||||
|
TodoTaskAdminServiceImpl service = dependencies.createService();
|
||||||
|
when(dependencies.userMapper.selectUserById(8L)).thenReturn(activeUser(8L));
|
||||||
|
when(dependencies.todoTaskService.createTask(any(TodoTaskCreateCommand.class)))
|
||||||
|
.thenReturn(new SysTodoTask());
|
||||||
|
TodoTaskAdminCreateRequest request = createRequest();
|
||||||
|
|
||||||
|
service.create(request, "admin");
|
||||||
|
|
||||||
|
ArgumentCaptor<TodoTaskCreateCommand> captor = ArgumentCaptor.forClass(TodoTaskCreateCommand.class);
|
||||||
|
verify(dependencies.todoTaskService).createTask(captor.capture());
|
||||||
|
assertEquals("MANUAL_TASK:MANUAL_BUSINESS:B100:8", captor.getValue().getIdempotentKey());
|
||||||
|
assertEquals("admin", captor.getValue().getCreateBy());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void assign_shouldUpdateActiveTaskAndResetAssignmentKey() {
|
||||||
|
Dependencies dependencies = new Dependencies();
|
||||||
|
TodoTaskAdminServiceImpl service = dependencies.createService();
|
||||||
|
when(dependencies.userMapper.selectUserById(9L)).thenReturn(activeUser(9L));
|
||||||
|
SysTodoTask task = task(TodoTaskConstants.STATUS_PENDING);
|
||||||
|
when(dependencies.todoTaskMapper.selectTodoById(20L)).thenReturn(task);
|
||||||
|
when(dependencies.todoTaskMapper.assignTodo(20L, 9L, "100",
|
||||||
|
"MANUAL_TASK:MANUAL_BUSINESS:B100:9", "admin")).thenReturn(1);
|
||||||
|
TodoTaskAssignRequest request = new TodoTaskAssignRequest();
|
||||||
|
request.setAssigneeUserId(9L);
|
||||||
|
request.setAssigneeMerchantId("100");
|
||||||
|
|
||||||
|
assertTrue(service.assign(20L, request, "admin"));
|
||||||
|
|
||||||
|
verify(dependencies.todoTaskMapper).assignTodo(20L, 9L, "100",
|
||||||
|
"MANUAL_TASK:MANUAL_BUSINESS:B100:9", "admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hide_shouldOnlyAllowTerminalTask() {
|
||||||
|
Dependencies dependencies = new Dependencies();
|
||||||
|
TodoTaskAdminServiceImpl service = dependencies.createService();
|
||||||
|
when(dependencies.todoTaskMapper.selectTodoById(20L))
|
||||||
|
.thenReturn(task(TodoTaskConstants.STATUS_PENDING));
|
||||||
|
|
||||||
|
assertThrows(ServiceException.class, () -> service.hide(20L, "admin"));
|
||||||
|
|
||||||
|
when(dependencies.todoTaskMapper.selectTodoById(21L))
|
||||||
|
.thenReturn(task(TodoTaskConstants.STATUS_CANCELLED));
|
||||||
|
when(dependencies.todoTaskMapper.hideTodo(21L, "admin")).thenReturn(1);
|
||||||
|
assertTrue(service.hide(21L, "admin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TodoTaskAdminCreateRequest createRequest() {
|
||||||
|
TodoTaskAdminCreateRequest request = new TodoTaskAdminCreateRequest();
|
||||||
|
request.setTaskType("MANUAL_TASK");
|
||||||
|
request.setTitle("人工待办");
|
||||||
|
request.setBusinessType("MANUAL_BUSINESS");
|
||||||
|
request.setBusinessId("B100");
|
||||||
|
request.setRouteName("TodoCenter");
|
||||||
|
request.setAssigneeUserId(8L);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SysTodoTask task(String status) {
|
||||||
|
SysTodoTask task = new SysTodoTask();
|
||||||
|
task.setTodoId(20L);
|
||||||
|
task.setTaskType("MANUAL_TASK");
|
||||||
|
task.setBusinessType("MANUAL_BUSINESS");
|
||||||
|
task.setBusinessId("B100");
|
||||||
|
task.setTaskStatus(status);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SysUser activeUser(Long userId) {
|
||||||
|
SysUser user = new SysUser();
|
||||||
|
user.setUserId(userId);
|
||||||
|
user.setStatus(Constants.ZERO);
|
||||||
|
user.setDelFlag(Constants.ZERO);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setField(Object target, String fieldName, Object value) {
|
||||||
|
try {
|
||||||
|
Field field = TodoTaskAdminServiceImpl.class.getDeclaredField(fieldName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.set(target, value);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new RuntimeException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Dependencies {
|
||||||
|
private final SysTodoTaskMapper todoTaskMapper = mock(SysTodoTaskMapper.class);
|
||||||
|
private final SysUserMapper userMapper = mock(SysUserMapper.class);
|
||||||
|
private final TodoTaskService todoTaskService = mock(TodoTaskService.class);
|
||||||
|
|
||||||
|
private TodoTaskAdminServiceImpl createService() {
|
||||||
|
TodoTaskAdminServiceImpl service = new TodoTaskAdminServiceImpl();
|
||||||
|
setField(service, "todoTaskMapper", todoTaskMapper);
|
||||||
|
setField(service, "userMapper", userMapper);
|
||||||
|
setField(service, "todoTaskService", todoTaskService);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.jsowell.web.controller.system;
|
||||||
|
|
||||||
|
import com.jsowell.system.domain.SysTodoTask;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAdminCreateRequest;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskCancelRequest;
|
||||||
|
import com.jsowell.system.service.TodoTaskAdminService;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class SysTodoTaskAdminControllerTest {
|
||||||
|
@Test
|
||||||
|
void create_shouldPassCurrentOperator() {
|
||||||
|
TodoTaskAdminService service = mock(TodoTaskAdminService.class);
|
||||||
|
TestController controller = newController(service, 1L, "admin");
|
||||||
|
TodoTaskAdminCreateRequest request = new TodoTaskAdminCreateRequest();
|
||||||
|
when(service.create(request, "admin")).thenReturn(new SysTodoTask());
|
||||||
|
|
||||||
|
controller.create(request);
|
||||||
|
|
||||||
|
verify(service).create(request, "admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void cancel_shouldPassCurrentUserAndOperator() {
|
||||||
|
TodoTaskAdminService service = mock(TodoTaskAdminService.class);
|
||||||
|
TestController controller = newController(service, 1L, "admin");
|
||||||
|
TodoTaskCancelRequest request = new TodoTaskCancelRequest();
|
||||||
|
request.setReason("异常任务关闭");
|
||||||
|
when(service.cancel(20L, 1L, "admin", "异常任务关闭")).thenReturn(true);
|
||||||
|
|
||||||
|
controller.cancel(20L, request);
|
||||||
|
|
||||||
|
verify(service).cancel(20L, 1L, "admin", "异常任务关闭");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TestController newController(TodoTaskAdminService service,
|
||||||
|
Long userId, String username) {
|
||||||
|
TestController controller = new TestController(userId, username);
|
||||||
|
try {
|
||||||
|
Field field = SysTodoTaskAdminController.class.getDeclaredField("todoTaskAdminService");
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.set(controller, service);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new RuntimeException(exception);
|
||||||
|
}
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestController extends SysTodoTaskAdminController {
|
||||||
|
private final Long userId;
|
||||||
|
private final String username;
|
||||||
|
|
||||||
|
private TestController(Long userId, String username) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.jsowell.system.domain.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Max;
|
||||||
|
import javax.validation.constraints.Min;
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理端人工创建待办请求。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class TodoTaskAdminCreateRequest implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@NotBlank(message = "待办类型不能为空")
|
||||||
|
@Size(max = 64, message = "待办类型不能超过64个字符")
|
||||||
|
private String taskType;
|
||||||
|
|
||||||
|
@NotBlank(message = "待办标题不能为空")
|
||||||
|
@Size(max = 200, message = "待办标题不能超过200个字符")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Size(max = 500, message = "待办摘要不能超过500个字符")
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
@Size(max = 20000, message = "待办内容不能超过20000个字符")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@NotBlank(message = "业务类型不能为空")
|
||||||
|
@Size(max = 64, message = "业务类型不能超过64个字符")
|
||||||
|
private String businessType;
|
||||||
|
|
||||||
|
@NotBlank(message = "业务主键不能为空")
|
||||||
|
@Size(max = 64, message = "业务主键不能超过64个字符")
|
||||||
|
private String businessId;
|
||||||
|
|
||||||
|
@NotBlank(message = "业务路由不能为空")
|
||||||
|
@Size(max = 100, message = "业务路由不能超过100个字符")
|
||||||
|
private String routeName;
|
||||||
|
|
||||||
|
@Size(max = 1000, message = "路由参数不能超过1000个字符")
|
||||||
|
private String routeParams;
|
||||||
|
|
||||||
|
@NotNull(message = "接收用户不能为空")
|
||||||
|
@Min(value = 1, message = "接收用户不能为空")
|
||||||
|
private Long assigneeUserId;
|
||||||
|
|
||||||
|
@Size(max = 64, message = "运营商 ID 不能超过64个字符")
|
||||||
|
private String assigneeMerchantId;
|
||||||
|
|
||||||
|
@Min(value = 1, message = "优先级只能为1、2或3")
|
||||||
|
@Max(value = 3, message = "优先级只能为1、2或3")
|
||||||
|
private Integer priority;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date dueTime;
|
||||||
|
|
||||||
|
@Size(max = 200, message = "幂等键不能超过200个字符")
|
||||||
|
private String idempotentKey;
|
||||||
|
|
||||||
|
@Size(max = 500, message = "备注不能超过500个字符")
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.jsowell.system.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理端待办查询条件。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class TodoTaskAdminQuery implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long assigneeUserId;
|
||||||
|
private String assigneeMerchantId;
|
||||||
|
private String taskStatus;
|
||||||
|
private String readStatus;
|
||||||
|
private String taskType;
|
||||||
|
private String keyword;
|
||||||
|
private Boolean hideCompleted;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.jsowell.system.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Min;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理端待办分配请求。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class TodoTaskAssignRequest implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@NotNull(message = "接收用户不能为空")
|
||||||
|
@Min(value = 1, message = "接收用户不能为空")
|
||||||
|
private Long assigneeUserId;
|
||||||
|
|
||||||
|
@Size(max = 64, message = "运营商 ID 不能超过64个字符")
|
||||||
|
private String assigneeMerchantId;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.jsowell.system.mapper;
|
package com.jsowell.system.mapper;
|
||||||
|
|
||||||
import com.jsowell.system.domain.SysTodoTask;
|
import com.jsowell.system.domain.SysTodoTask;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAdminQuery;
|
||||||
import com.jsowell.system.domain.dto.TodoTaskQuery;
|
import com.jsowell.system.domain.dto.TodoTaskQuery;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
@@ -21,6 +22,8 @@ public interface SysTodoTaskMapper {
|
|||||||
|
|
||||||
List<SysTodoTask> selectTodoList(TodoTaskQuery query);
|
List<SysTodoTask> selectTodoList(TodoTaskQuery query);
|
||||||
|
|
||||||
|
List<SysTodoTask> selectAdminTodoList(TodoTaskAdminQuery query);
|
||||||
|
|
||||||
List<SysTodoTask> selectHomeList(@Param("assigneeUserId") Long assigneeUserId,
|
List<SysTodoTask> selectHomeList(@Param("assigneeUserId") Long assigneeUserId,
|
||||||
@Param("limit") int limit);
|
@Param("limit") int limit);
|
||||||
|
|
||||||
@@ -62,4 +65,12 @@ public interface SysTodoTaskMapper {
|
|||||||
@Param("assigneeUserId") Long assigneeUserId,
|
@Param("assigneeUserId") Long assigneeUserId,
|
||||||
@Param("reason") String reason,
|
@Param("reason") String reason,
|
||||||
@Param("updateBy") String updateBy);
|
@Param("updateBy") String updateBy);
|
||||||
|
|
||||||
|
int assignTodo(@Param("todoId") Long todoId,
|
||||||
|
@Param("assigneeUserId") Long assigneeUserId,
|
||||||
|
@Param("assigneeMerchantId") String assigneeMerchantId,
|
||||||
|
@Param("activeIdempotentKey") String activeIdempotentKey,
|
||||||
|
@Param("updateBy") String updateBy);
|
||||||
|
|
||||||
|
int hideTodo(@Param("todoId") Long todoId, @Param("updateBy") String updateBy);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.jsowell.system.service;
|
||||||
|
|
||||||
|
import com.jsowell.system.domain.SysTodoTask;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAdminCreateRequest;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAdminQuery;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAssignRequest;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待办管理端服务。
|
||||||
|
*/
|
||||||
|
public interface TodoTaskAdminService {
|
||||||
|
List<SysTodoTask> list(TodoTaskAdminQuery query);
|
||||||
|
|
||||||
|
SysTodoTask create(TodoTaskAdminCreateRequest request, String operatorName);
|
||||||
|
|
||||||
|
boolean assign(Long todoId, TodoTaskAssignRequest request, String operatorName);
|
||||||
|
|
||||||
|
boolean cancel(Long todoId, Long operatorId, String operatorName, String reason);
|
||||||
|
|
||||||
|
boolean hide(Long todoId, String operatorName);
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package com.jsowell.system.service.impl;
|
||||||
|
|
||||||
|
import com.jsowell.common.constant.Constants;
|
||||||
|
import com.jsowell.common.core.domain.entity.SysUser;
|
||||||
|
import com.jsowell.common.exception.ServiceException;
|
||||||
|
import com.jsowell.common.util.StringUtils;
|
||||||
|
import com.jsowell.system.constant.TodoTaskConstants;
|
||||||
|
import com.jsowell.system.domain.SysTodoTask;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAdminCreateRequest;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAdminQuery;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskAssignRequest;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskCreateCommand;
|
||||||
|
import com.jsowell.system.mapper.SysTodoTaskMapper;
|
||||||
|
import com.jsowell.system.mapper.SysUserMapper;
|
||||||
|
import com.jsowell.system.service.TodoTaskAdminService;
|
||||||
|
import com.jsowell.system.service.TodoTaskService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待办管理端服务实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class TodoTaskAdminServiceImpl implements TodoTaskAdminService {
|
||||||
|
@Autowired
|
||||||
|
private SysTodoTaskMapper todoTaskMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysUserMapper userMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TodoTaskService todoTaskService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SysTodoTask> list(TodoTaskAdminQuery query) {
|
||||||
|
TodoTaskAdminQuery safeQuery = query == null ? new TodoTaskAdminQuery() : query;
|
||||||
|
validateQuery(safeQuery);
|
||||||
|
return todoTaskMapper.selectAdminTodoList(safeQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public SysTodoTask create(TodoTaskAdminCreateRequest request, String operatorName) {
|
||||||
|
if (request == null) {
|
||||||
|
throw new ServiceException("待办创建参数不能为空");
|
||||||
|
}
|
||||||
|
validateActiveUser(request.getAssigneeUserId());
|
||||||
|
String idempotentKey = StringUtils.isNotBlank(request.getIdempotentKey())
|
||||||
|
? request.getIdempotentKey().trim()
|
||||||
|
: buildAssignmentKey(request.getTaskType(), request.getBusinessType(),
|
||||||
|
request.getBusinessId(), request.getAssigneeUserId());
|
||||||
|
return todoTaskService.createTask(TodoTaskCreateCommand.builder()
|
||||||
|
.taskType(request.getTaskType())
|
||||||
|
.title(request.getTitle())
|
||||||
|
.summary(request.getSummary())
|
||||||
|
.content(request.getContent())
|
||||||
|
.businessType(request.getBusinessType())
|
||||||
|
.businessId(request.getBusinessId())
|
||||||
|
.routeName(request.getRouteName())
|
||||||
|
.routeParams(request.getRouteParams())
|
||||||
|
.assigneeUserId(request.getAssigneeUserId())
|
||||||
|
.assigneeMerchantId(request.getAssigneeMerchantId())
|
||||||
|
.priority(request.getPriority())
|
||||||
|
.dueTime(request.getDueTime())
|
||||||
|
.idempotentKey(idempotentKey)
|
||||||
|
.createBy(normalizeOperator(operatorName))
|
||||||
|
.remark(request.getRemark())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean assign(Long todoId, TodoTaskAssignRequest request, String operatorName) {
|
||||||
|
if (request == null) {
|
||||||
|
throw new ServiceException("待办分配参数不能为空");
|
||||||
|
}
|
||||||
|
validateTodoId(todoId);
|
||||||
|
validateActiveUser(request.getAssigneeUserId());
|
||||||
|
SysTodoTask todoTask = requireTask(todoId);
|
||||||
|
if (!TodoTaskConstants.isActiveStatus(todoTask.getTaskStatus())) {
|
||||||
|
throw new ServiceException("只有待处理任务可以重新分配");
|
||||||
|
}
|
||||||
|
String activeIdempotentKey = buildAssignmentKey(todoTask.getTaskType(),
|
||||||
|
todoTask.getBusinessType(), todoTask.getBusinessId(), request.getAssigneeUserId());
|
||||||
|
String assigneeMerchantId = StringUtils.isBlank(request.getAssigneeMerchantId())
|
||||||
|
? todoTask.getAssigneeMerchantId()
|
||||||
|
: normalizeOptional(request.getAssigneeMerchantId(), 64);
|
||||||
|
try {
|
||||||
|
int rows = todoTaskMapper.assignTodo(todoId, request.getAssigneeUserId(), assigneeMerchantId,
|
||||||
|
activeIdempotentKey,
|
||||||
|
normalizeOperator(operatorName));
|
||||||
|
if (rows > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (DuplicateKeyException exception) {
|
||||||
|
throw new ServiceException("目标用户已存在同一业务的有效待办");
|
||||||
|
}
|
||||||
|
throw new ServiceException("当前待办状态不允许重新分配");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean cancel(Long todoId, Long operatorId, String operatorName, String reason) {
|
||||||
|
return todoTaskService.cancel(todoId, operatorId, operatorName, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean hide(Long todoId, String operatorName) {
|
||||||
|
validateTodoId(todoId);
|
||||||
|
SysTodoTask todoTask = requireTask(todoId);
|
||||||
|
if (!TodoTaskConstants.isTerminalStatus(todoTask.getTaskStatus())) {
|
||||||
|
throw new ServiceException("待处理任务需先取消后才能隐藏");
|
||||||
|
}
|
||||||
|
if (todoTaskMapper.hideTodo(todoId, normalizeOperator(operatorName)) > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new ServiceException("当前待办状态不允许隐藏");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateQuery(TodoTaskAdminQuery query) {
|
||||||
|
if (query.getAssigneeUserId() != null && query.getAssigneeUserId() <= 0) {
|
||||||
|
throw new ServiceException("接收用户 ID 不合法");
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(query.getTaskStatus())
|
||||||
|
&& !TodoTaskConstants.isSupportedStatus(query.getTaskStatus())) {
|
||||||
|
throw new ServiceException("不支持的待办状态筛选条件");
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(query.getReadStatus())
|
||||||
|
&& !TodoTaskConstants.READ_UNREAD.equals(query.getReadStatus())
|
||||||
|
&& !TodoTaskConstants.READ_READ.equals(query.getReadStatus())) {
|
||||||
|
throw new ServiceException("不支持的阅读状态筛选条件");
|
||||||
|
}
|
||||||
|
query.setAssigneeMerchantId(normalizeOptional(query.getAssigneeMerchantId(), 64));
|
||||||
|
query.setTaskType(normalizeOptional(query.getTaskType(), 64));
|
||||||
|
query.setKeyword(normalizeOptional(query.getKeyword(), 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SysTodoTask requireTask(Long todoId) {
|
||||||
|
SysTodoTask todoTask = todoTaskMapper.selectTodoById(todoId);
|
||||||
|
if (todoTask == null) {
|
||||||
|
throw new ServiceException("待办任务不存在");
|
||||||
|
}
|
||||||
|
return todoTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateActiveUser(Long userId) {
|
||||||
|
if (userId == null || userId <= 0) {
|
||||||
|
throw new ServiceException("接收用户不能为空");
|
||||||
|
}
|
||||||
|
SysUser user = userMapper.selectUserById(userId);
|
||||||
|
if (user == null || !Constants.ZERO.equals(user.getStatus())
|
||||||
|
|| !Constants.ZERO.equals(user.getDelFlag())) {
|
||||||
|
throw new ServiceException("接收用户不存在或已停用");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateTodoId(Long todoId) {
|
||||||
|
if (todoId == null || todoId <= 0) {
|
||||||
|
throw new ServiceException("待办任务 ID 不能为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildAssignmentKey(String taskType, String businessType,
|
||||||
|
String businessId, Long assigneeUserId) {
|
||||||
|
String source = StringUtils.trim(taskType) + ":" + StringUtils.trim(businessType)
|
||||||
|
+ ":" + StringUtils.trim(businessId) + ":" + assigneeUserId;
|
||||||
|
if (source.length() <= 200) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
return "ADMIN_ASSIGN:" + sha256(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sha256(String value) {
|
||||||
|
try {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] bytes = digest.digest(value.getBytes(StandardCharsets.UTF_8));
|
||||||
|
StringBuilder result = new StringBuilder(bytes.length * 2);
|
||||||
|
for (byte item : bytes) {
|
||||||
|
result.append(String.format("%02x", item & 0xff));
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
} catch (NoSuchAlgorithmException exception) {
|
||||||
|
throw new IllegalStateException("当前运行环境不支持 SHA-256", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeOptional(String value, int maxLength) {
|
||||||
|
if (StringUtils.isEmpty(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String safeValue = value.trim();
|
||||||
|
if (safeValue.length() > maxLength) {
|
||||||
|
throw new ServiceException("查询条件长度不能超过" + maxLength + "个字符");
|
||||||
|
}
|
||||||
|
return safeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeOperator(String operatorName) {
|
||||||
|
String safeOperator = StringUtils.isNotEmpty(operatorName) ? operatorName.trim() : "system";
|
||||||
|
if (safeOperator.length() > 64) {
|
||||||
|
throw new ServiceException("操作人名称不能超过64个字符");
|
||||||
|
}
|
||||||
|
return safeOperator;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -93,6 +93,40 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
order by priority desc, create_time desc, todo_id desc
|
order by priority desc, create_time desc, todo_id desc
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectAdminTodoList" parameterType="com.jsowell.system.domain.dto.TodoTaskAdminQuery"
|
||||||
|
resultMap="SysTodoTaskResult">
|
||||||
|
<include refid="selectTodoVo" />
|
||||||
|
<where>
|
||||||
|
del_flag = '0'
|
||||||
|
<if test="assigneeUserId != null">
|
||||||
|
and assignee_user_id = #{assigneeUserId}
|
||||||
|
</if>
|
||||||
|
<if test="assigneeMerchantId != null and assigneeMerchantId != ''">
|
||||||
|
and assignee_merchant_id = #{assigneeMerchantId}
|
||||||
|
</if>
|
||||||
|
<if test="taskStatus != null and taskStatus == 'ACTIVE'">
|
||||||
|
and task_status in ('PENDING', 'PROCESSING')
|
||||||
|
</if>
|
||||||
|
<if test="taskStatus != null and taskStatus != '' and taskStatus != 'ACTIVE'">
|
||||||
|
and task_status = #{taskStatus}
|
||||||
|
</if>
|
||||||
|
<if test="readStatus != null and readStatus != ''">
|
||||||
|
and read_status = #{readStatus}
|
||||||
|
</if>
|
||||||
|
<if test="taskType != null and taskType != ''">
|
||||||
|
and task_type = #{taskType}
|
||||||
|
</if>
|
||||||
|
<if test="keyword != null and keyword != ''">
|
||||||
|
and (title like concat('%', #{keyword}, '%')
|
||||||
|
or business_id like concat('%', #{keyword}, '%'))
|
||||||
|
</if>
|
||||||
|
<if test="hideCompleted != null and hideCompleted">
|
||||||
|
and task_status != 'COMPLETED'
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
order by priority desc, create_time desc, todo_id desc
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectHomeList" resultMap="SysTodoTaskResult">
|
<select id="selectHomeList" resultMap="SysTodoTaskResult">
|
||||||
<include refid="selectTodoVo" />
|
<include refid="selectTodoVo" />
|
||||||
where assignee_user_id = #{assigneeUserId}
|
where assignee_user_id = #{assigneeUserId}
|
||||||
@@ -228,4 +262,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
and del_flag = '0'
|
and del_flag = '0'
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<update id="assignTodo">
|
||||||
|
update sys_todo_task
|
||||||
|
set assignee_user_id = #{assigneeUserId},
|
||||||
|
assignee_merchant_id = #{assigneeMerchantId},
|
||||||
|
read_status = '0',
|
||||||
|
read_time = null,
|
||||||
|
active_idempotent_key = #{activeIdempotentKey},
|
||||||
|
update_by = #{updateBy},
|
||||||
|
update_time = sysdate()
|
||||||
|
where todo_id = #{todoId}
|
||||||
|
and task_status in ('PENDING', 'PROCESSING')
|
||||||
|
and del_flag = '0'
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="hideTodo">
|
||||||
|
update sys_todo_task
|
||||||
|
set active_idempotent_key = null,
|
||||||
|
del_flag = '2',
|
||||||
|
update_by = #{updateBy},
|
||||||
|
update_time = sysdate()
|
||||||
|
where todo_id = #{todoId}
|
||||||
|
and task_status in ('COMPLETED', 'CANCELLED', 'EXPIRED')
|
||||||
|
and del_flag = '0'
|
||||||
|
</update>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
Reference in New Issue
Block a user