mirror of
https://codeup.aliyun.com/67c68d4e484ca2f0a13ac3c1/ydc/jsowell-charger-web.git
synced 2026-07-18 21:18:01 +08:00
新增待办任务通用领域服务
This commit is contained in:
@@ -41,8 +41,8 @@ PRD 同时要求“幂等键唯一”和“任务完成后允许同一业务重
|
|||||||
|
|
||||||
| 批次 | 范围 | 优先级 | 状态 | 验收结果 | Commit |
|
| 批次 | 范围 | 优先级 | 状态 | 验收结果 | Commit |
|
||||||
| --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- |
|
||||||
| B01 | 任务拆分、数据表和索引 | P0 | done | SQL 结构和索引评审通过 | 本批提交 |
|
| B01 | 任务拆分、数据表和索引 | P0 | done | SQL 结构和索引评审通过 | `e65017ab2` |
|
||||||
| B02 | 实体、命令模型、枚举、Mapper、Service | P0 | todo | - | - |
|
| B02 | 实体、命令模型、枚举、Mapper、Service | P0 | done | 模块编译和 Mapper XML 校验通过 | 本批提交 |
|
||||||
| B03 | 当前用户查询、已读、完成、取消接口及测试 | P0 | todo | - | - |
|
| B03 | 当前用户查询、已读、完成、取消接口及测试 | P0 | todo | - | - |
|
||||||
| B04 | 前端 API、待办中心页面和路由跳转 | P0 | todo | - | - |
|
| B04 | 前端 API、待办中心页面和路由跳转 | P0 | todo | - | - |
|
||||||
| B05 | 首页待办卡片、数量展示和轮询刷新 | P0 | todo | - | - |
|
| B05 | 首页待办卡片、数量展示和轮询刷新 | P0 | todo | - | - |
|
||||||
@@ -72,7 +72,7 @@ PRD 同时要求“幂等键唯一”和“任务完成后允许同一业务重
|
|||||||
|
|
||||||
- 优先级:P0
|
- 优先级:P0
|
||||||
- 批次:B02
|
- 批次:B02
|
||||||
- 状态:todo
|
- 状态:done
|
||||||
- 目标:新增实体、创建命令、状态常量、Mapper 和统一 `TodoTaskService`。
|
- 目标:新增实体、创建命令、状态常量、Mapper 和统一 `TodoTaskService`。
|
||||||
- 验收标准:
|
- 验收标准:
|
||||||
- 创建接口校验必要字段并设置默认状态、优先级和阅读状态。
|
- 创建接口校验必要字段并设置默认状态、优先级和阅读状态。
|
||||||
@@ -173,4 +173,6 @@ PRD 同时要求“幂等键唯一”和“任务完成后允许同一业务重
|
|||||||
|
|
||||||
每完成一个批次,在此追加:完成时间、验证命令、结果和 commit hash。
|
每完成一个批次,在此追加:完成时间、验证命令、结果和 commit hash。
|
||||||
|
|
||||||
- 2026-07-16,B01:完成优先级拆分、任务跟踪文档和 `sys_todo_task` 建表脚本;通过 SQL 人工结构评审与 `git diff --check`,commit hash 在下一批记录中回填。
|
- 2026-07-16,B01:完成优先级拆分、任务跟踪文档和 `sys_todo_task` 建表脚本;通过 SQL 人工结构评审与 `git diff --check`;commit `e65017ab2`。
|
||||||
|
- 2026-07-16,B01 环境进展:用户已在测试数据库创建 `sys_todo_task` 表,可供后续接口联调。
|
||||||
|
- 2026-07-16,B02:完成领域模型、创建/查询 DTO、状态常量、Mapper 和统一 Service;`mvn -pl jsowell-system -am -DskipTests compile` 与 Mapper XML 语法校验通过;commit hash 在下一批记录中回填。
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.jsowell.system.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待办任务常量。
|
||||||
|
*
|
||||||
|
* @author jsowell
|
||||||
|
*/
|
||||||
|
public final class TodoTaskConstants {
|
||||||
|
private TodoTaskConstants() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String STATUS_PENDING = "PENDING";
|
||||||
|
public static final String STATUS_PROCESSING = "PROCESSING";
|
||||||
|
public static final String STATUS_COMPLETED = "COMPLETED";
|
||||||
|
public static final String STATUS_CANCELLED = "CANCELLED";
|
||||||
|
public static final String STATUS_EXPIRED = "EXPIRED";
|
||||||
|
public static final String STATUS_ACTIVE = "ACTIVE";
|
||||||
|
|
||||||
|
public static final String READ_UNREAD = "0";
|
||||||
|
public static final String READ_READ = "1";
|
||||||
|
|
||||||
|
public static final int PRIORITY_NORMAL = 1;
|
||||||
|
public static final int PRIORITY_IMPORTANT = 2;
|
||||||
|
public static final int PRIORITY_URGENT = 3;
|
||||||
|
|
||||||
|
public static final int DEFAULT_HOME_LIMIT = 3;
|
||||||
|
public static final int MAX_HOME_LIMIT = 10;
|
||||||
|
|
||||||
|
public static boolean isActiveStatus(String status) {
|
||||||
|
return STATUS_PENDING.equals(status) || STATUS_PROCESSING.equals(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isTerminalStatus(String status) {
|
||||||
|
return STATUS_COMPLETED.equals(status)
|
||||||
|
|| STATUS_CANCELLED.equals(status)
|
||||||
|
|| STATUS_EXPIRED.equals(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSupportedStatus(String status) {
|
||||||
|
return STATUS_ACTIVE.equals(status)
|
||||||
|
|| isActiveStatus(status)
|
||||||
|
|| isTerminalStatus(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.jsowell.system.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.jsowell.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户待办任务表 sys_todo_task。
|
||||||
|
*
|
||||||
|
* @author jsowell
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SysTodoTask extends BaseEntity {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long todoId;
|
||||||
|
private String taskType;
|
||||||
|
private String title;
|
||||||
|
private String summary;
|
||||||
|
private String content;
|
||||||
|
private String businessType;
|
||||||
|
private String businessId;
|
||||||
|
private String routeName;
|
||||||
|
private String routeParams;
|
||||||
|
private Long assigneeUserId;
|
||||||
|
private String assigneeMerchantId;
|
||||||
|
private Integer priority;
|
||||||
|
private String taskStatus;
|
||||||
|
private String readStatus;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date readTime;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date dueTime;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date completedTime;
|
||||||
|
|
||||||
|
private Long completedBy;
|
||||||
|
private String cancelReason;
|
||||||
|
private String idempotentKey;
|
||||||
|
private String activeIdempotentKey;
|
||||||
|
private String delFlag;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.jsowell.system.domain.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建待办任务命令。
|
||||||
|
*
|
||||||
|
* @author jsowell
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TodoTaskCreateCommand implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private String taskType;
|
||||||
|
private String title;
|
||||||
|
private String summary;
|
||||||
|
private String content;
|
||||||
|
private String businessType;
|
||||||
|
private String businessId;
|
||||||
|
private String routeName;
|
||||||
|
private String routeParams;
|
||||||
|
private Long assigneeUserId;
|
||||||
|
private String assigneeMerchantId;
|
||||||
|
private Integer priority;
|
||||||
|
private Date dueTime;
|
||||||
|
private String idempotentKey;
|
||||||
|
private String createBy;
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.jsowell.system.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户待办查询条件。
|
||||||
|
*
|
||||||
|
* @author jsowell
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class TodoTaskQuery implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 由服务端覆盖,不信任请求参数。 */
|
||||||
|
private Long assigneeUserId;
|
||||||
|
private String taskStatus;
|
||||||
|
private String readStatus;
|
||||||
|
private String taskType;
|
||||||
|
private String keyword;
|
||||||
|
private Boolean hideCompleted;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.jsowell.system.mapper;
|
||||||
|
|
||||||
|
import com.jsowell.system.domain.SysTodoTask;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskQuery;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待办任务数据层。
|
||||||
|
*
|
||||||
|
* @author jsowell
|
||||||
|
*/
|
||||||
|
public interface SysTodoTaskMapper {
|
||||||
|
SysTodoTask selectTodoById(@Param("todoId") Long todoId);
|
||||||
|
|
||||||
|
SysTodoTask selectTodoByIdForAssignee(@Param("todoId") Long todoId,
|
||||||
|
@Param("assigneeUserId") Long assigneeUserId);
|
||||||
|
|
||||||
|
SysTodoTask selectActiveByIdempotentKey(@Param("activeIdempotentKey") String activeIdempotentKey);
|
||||||
|
|
||||||
|
List<SysTodoTask> selectTodoList(TodoTaskQuery query);
|
||||||
|
|
||||||
|
List<SysTodoTask> selectHomeList(@Param("assigneeUserId") Long assigneeUserId,
|
||||||
|
@Param("limit") int limit);
|
||||||
|
|
||||||
|
int countActive(@Param("assigneeUserId") Long assigneeUserId);
|
||||||
|
|
||||||
|
int countUnread(@Param("assigneeUserId") Long assigneeUserId);
|
||||||
|
|
||||||
|
int insertTodoTask(SysTodoTask todoTask);
|
||||||
|
|
||||||
|
int markTodoRead(@Param("todoId") Long todoId,
|
||||||
|
@Param("assigneeUserId") Long assigneeUserId,
|
||||||
|
@Param("updateBy") String updateBy);
|
||||||
|
|
||||||
|
int markAllRead(@Param("assigneeUserId") Long assigneeUserId,
|
||||||
|
@Param("updateBy") String updateBy);
|
||||||
|
|
||||||
|
int completeTodoForAssignee(@Param("todoId") Long todoId,
|
||||||
|
@Param("assigneeUserId") Long assigneeUserId,
|
||||||
|
@Param("completedBy") Long completedBy,
|
||||||
|
@Param("updateBy") String updateBy);
|
||||||
|
|
||||||
|
int completeTodoByBusiness(@Param("businessType") String businessType,
|
||||||
|
@Param("businessId") String businessId,
|
||||||
|
@Param("assigneeUserId") Long assigneeUserId,
|
||||||
|
@Param("completedBy") Long completedBy,
|
||||||
|
@Param("updateBy") String updateBy);
|
||||||
|
|
||||||
|
int cancelTodoForAssignee(@Param("todoId") Long todoId,
|
||||||
|
@Param("assigneeUserId") Long assigneeUserId,
|
||||||
|
@Param("reason") String reason,
|
||||||
|
@Param("updateBy") String updateBy);
|
||||||
|
|
||||||
|
int cancelTodo(@Param("todoId") Long todoId,
|
||||||
|
@Param("reason") String reason,
|
||||||
|
@Param("updateBy") String updateBy);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.jsowell.system.service;
|
||||||
|
|
||||||
|
import com.jsowell.system.domain.SysTodoTask;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskCreateCommand;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskQuery;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一待办任务服务。
|
||||||
|
*
|
||||||
|
* @author jsowell
|
||||||
|
*/
|
||||||
|
public interface TodoTaskService {
|
||||||
|
SysTodoTask createTask(TodoTaskCreateCommand command);
|
||||||
|
|
||||||
|
SysTodoTask getForAssignee(Long todoId, Long assigneeUserId);
|
||||||
|
|
||||||
|
List<SysTodoTask> listForAssignee(TodoTaskQuery query, Long assigneeUserId);
|
||||||
|
|
||||||
|
List<SysTodoTask> homeList(Long assigneeUserId, Integer limit);
|
||||||
|
|
||||||
|
int countActive(Long assigneeUserId);
|
||||||
|
|
||||||
|
int countUnread(Long assigneeUserId);
|
||||||
|
|
||||||
|
boolean markRead(Long todoId, Long assigneeUserId, String operatorName);
|
||||||
|
|
||||||
|
int markAllRead(Long assigneeUserId, String operatorName);
|
||||||
|
|
||||||
|
boolean complete(Long todoId, Long assigneeUserId, String operatorName);
|
||||||
|
|
||||||
|
int completeByBusiness(String businessType, String businessId, Long assigneeUserId,
|
||||||
|
Long operatorId, String operatorName);
|
||||||
|
|
||||||
|
boolean cancelForAssignee(Long todoId, Long assigneeUserId, String operatorName, String reason);
|
||||||
|
|
||||||
|
boolean cancel(Long todoId, Long operatorId, String operatorName, String reason);
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
package com.jsowell.system.service.impl;
|
||||||
|
|
||||||
|
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.TodoTaskCreateCommand;
|
||||||
|
import com.jsowell.system.domain.dto.TodoTaskQuery;
|
||||||
|
import com.jsowell.system.mapper.SysTodoTaskMapper;
|
||||||
|
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.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一待办任务服务实现。
|
||||||
|
*
|
||||||
|
* @author jsowell
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class TodoTaskServiceImpl implements TodoTaskService {
|
||||||
|
private static final String SYSTEM_OPERATOR = "system";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysTodoTaskMapper todoTaskMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public SysTodoTask createTask(TodoTaskCreateCommand command) {
|
||||||
|
if (command == null) {
|
||||||
|
throw new ServiceException("待办创建参数不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
SysTodoTask todoTask = buildTask(command);
|
||||||
|
SysTodoTask existingTask = todoTaskMapper.selectActiveByIdempotentKey(todoTask.getActiveIdempotentKey());
|
||||||
|
if (existingTask != null) {
|
||||||
|
return existingTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
todoTaskMapper.insertTodoTask(todoTask);
|
||||||
|
return todoTask;
|
||||||
|
} catch (DuplicateKeyException exception) {
|
||||||
|
existingTask = todoTaskMapper.selectActiveByIdempotentKey(todoTask.getActiveIdempotentKey());
|
||||||
|
if (existingTask != null) {
|
||||||
|
return existingTask;
|
||||||
|
}
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SysTodoTask getForAssignee(Long todoId, Long assigneeUserId) {
|
||||||
|
validateIdentity(todoId, assigneeUserId);
|
||||||
|
SysTodoTask todoTask = todoTaskMapper.selectTodoByIdForAssignee(todoId, assigneeUserId);
|
||||||
|
if (todoTask == null) {
|
||||||
|
throw new ServiceException("待办任务不存在或无权访问");
|
||||||
|
}
|
||||||
|
return todoTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SysTodoTask> listForAssignee(TodoTaskQuery query, Long assigneeUserId) {
|
||||||
|
validateUserId(assigneeUserId);
|
||||||
|
TodoTaskQuery safeQuery = query == null ? new TodoTaskQuery() : query;
|
||||||
|
validateQuery(safeQuery);
|
||||||
|
safeQuery.setAssigneeUserId(assigneeUserId);
|
||||||
|
return todoTaskMapper.selectTodoList(safeQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SysTodoTask> homeList(Long assigneeUserId, Integer limit) {
|
||||||
|
validateUserId(assigneeUserId);
|
||||||
|
int safeLimit = limit == null ? TodoTaskConstants.DEFAULT_HOME_LIMIT : limit;
|
||||||
|
if (safeLimit < 1) {
|
||||||
|
safeLimit = TodoTaskConstants.DEFAULT_HOME_LIMIT;
|
||||||
|
}
|
||||||
|
if (safeLimit > TodoTaskConstants.MAX_HOME_LIMIT) {
|
||||||
|
safeLimit = TodoTaskConstants.MAX_HOME_LIMIT;
|
||||||
|
}
|
||||||
|
return todoTaskMapper.selectHomeList(assigneeUserId, safeLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int countActive(Long assigneeUserId) {
|
||||||
|
validateUserId(assigneeUserId);
|
||||||
|
return todoTaskMapper.countActive(assigneeUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int countUnread(Long assigneeUserId) {
|
||||||
|
validateUserId(assigneeUserId);
|
||||||
|
return todoTaskMapper.countUnread(assigneeUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean markRead(Long todoId, Long assigneeUserId, String operatorName) {
|
||||||
|
validateIdentity(todoId, assigneeUserId);
|
||||||
|
int rows = todoTaskMapper.markTodoRead(todoId, assigneeUserId, normalizeOperator(operatorName, assigneeUserId));
|
||||||
|
if (rows > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
SysTodoTask todoTask = getForAssignee(todoId, assigneeUserId);
|
||||||
|
return TodoTaskConstants.READ_READ.equals(todoTask.getReadStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public int markAllRead(Long assigneeUserId, String operatorName) {
|
||||||
|
validateUserId(assigneeUserId);
|
||||||
|
return todoTaskMapper.markAllRead(assigneeUserId, normalizeOperator(operatorName, assigneeUserId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean complete(Long todoId, Long assigneeUserId, String operatorName) {
|
||||||
|
validateIdentity(todoId, assigneeUserId);
|
||||||
|
int rows = todoTaskMapper.completeTodoForAssignee(todoId, assigneeUserId, assigneeUserId,
|
||||||
|
normalizeOperator(operatorName, assigneeUserId));
|
||||||
|
if (rows > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SysTodoTask todoTask = getForAssignee(todoId, assigneeUserId);
|
||||||
|
if (TodoTaskConstants.STATUS_COMPLETED.equals(todoTask.getTaskStatus())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new ServiceException("当前待办状态不允许完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public int completeByBusiness(String businessType, String businessId, Long assigneeUserId,
|
||||||
|
Long operatorId, String operatorName) {
|
||||||
|
String safeBusinessType = normalizeRequired(businessType, "业务类型", 64);
|
||||||
|
String safeBusinessId = normalizeRequired(businessId, "业务主键", 64);
|
||||||
|
if (assigneeUserId != null) {
|
||||||
|
validateUserId(assigneeUserId);
|
||||||
|
}
|
||||||
|
if (operatorId == null || operatorId <= 0) {
|
||||||
|
throw new ServiceException("待办完成人不能为空");
|
||||||
|
}
|
||||||
|
return todoTaskMapper.completeTodoByBusiness(safeBusinessType, safeBusinessId, assigneeUserId,
|
||||||
|
operatorId, normalizeOperator(operatorName, operatorId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean cancelForAssignee(Long todoId, Long assigneeUserId, String operatorName, String reason) {
|
||||||
|
validateIdentity(todoId, assigneeUserId);
|
||||||
|
String safeReason = normalizeRequired(reason, "取消原因", 500);
|
||||||
|
int rows = todoTaskMapper.cancelTodoForAssignee(todoId, assigneeUserId, safeReason,
|
||||||
|
normalizeOperator(operatorName, assigneeUserId));
|
||||||
|
if (rows > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SysTodoTask todoTask = getForAssignee(todoId, assigneeUserId);
|
||||||
|
if (TodoTaskConstants.STATUS_CANCELLED.equals(todoTask.getTaskStatus())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new ServiceException("当前待办状态不允许取消");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean cancel(Long todoId, Long operatorId, String operatorName, String reason) {
|
||||||
|
if (todoId == null || todoId <= 0) {
|
||||||
|
throw new ServiceException("待办任务 ID 不能为空");
|
||||||
|
}
|
||||||
|
if (operatorId == null || operatorId <= 0) {
|
||||||
|
throw new ServiceException("取消操作人不能为空");
|
||||||
|
}
|
||||||
|
String safeReason = normalizeRequired(reason, "取消原因", 500);
|
||||||
|
int rows = todoTaskMapper.cancelTodo(todoId, safeReason, normalizeOperator(operatorName, operatorId));
|
||||||
|
if (rows > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SysTodoTask todoTask = todoTaskMapper.selectTodoById(todoId);
|
||||||
|
if (todoTask == null) {
|
||||||
|
throw new ServiceException("待办任务不存在");
|
||||||
|
}
|
||||||
|
if (TodoTaskConstants.STATUS_CANCELLED.equals(todoTask.getTaskStatus())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new ServiceException("当前待办状态不允许取消");
|
||||||
|
}
|
||||||
|
|
||||||
|
private SysTodoTask buildTask(TodoTaskCreateCommand command) {
|
||||||
|
if (command.getAssigneeUserId() == null || command.getAssigneeUserId() <= 0) {
|
||||||
|
throw new ServiceException("待办接收用户不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer priority = command.getPriority() == null
|
||||||
|
? TodoTaskConstants.PRIORITY_NORMAL : command.getPriority();
|
||||||
|
if (priority < TodoTaskConstants.PRIORITY_NORMAL || priority > TodoTaskConstants.PRIORITY_URGENT) {
|
||||||
|
throw new ServiceException("待办优先级只能为 1、2 或 3");
|
||||||
|
}
|
||||||
|
|
||||||
|
SysTodoTask todoTask = new SysTodoTask();
|
||||||
|
todoTask.setTaskType(normalizeRequired(command.getTaskType(), "待办类型", 64));
|
||||||
|
todoTask.setTitle(normalizeRequired(command.getTitle(), "待办标题", 200));
|
||||||
|
todoTask.setSummary(normalizeOptional(command.getSummary(), "待办摘要", 500));
|
||||||
|
todoTask.setContent(normalizeOptional(command.getContent(), "待办内容", 20000));
|
||||||
|
todoTask.setBusinessType(normalizeRequired(command.getBusinessType(), "业务类型", 64));
|
||||||
|
todoTask.setBusinessId(normalizeRequired(command.getBusinessId(), "业务主键", 64));
|
||||||
|
todoTask.setRouteName(normalizeRequired(command.getRouteName(), "业务路由", 100));
|
||||||
|
todoTask.setRouteParams(normalizeOptional(command.getRouteParams(), "路由参数", 1000));
|
||||||
|
todoTask.setAssigneeUserId(command.getAssigneeUserId());
|
||||||
|
todoTask.setAssigneeMerchantId(normalizeOptional(command.getAssigneeMerchantId(), "运营商 ID", 64));
|
||||||
|
todoTask.setPriority(priority);
|
||||||
|
todoTask.setTaskStatus(TodoTaskConstants.STATUS_PENDING);
|
||||||
|
todoTask.setReadStatus(TodoTaskConstants.READ_UNREAD);
|
||||||
|
todoTask.setDueTime(command.getDueTime());
|
||||||
|
todoTask.setIdempotentKey(normalizeRequired(command.getIdempotentKey(), "幂等键", 200));
|
||||||
|
todoTask.setActiveIdempotentKey(todoTask.getIdempotentKey());
|
||||||
|
todoTask.setCreateBy(normalizeOperator(command.getCreateBy(), null));
|
||||||
|
todoTask.setRemark(normalizeOptional(command.getRemark(), "备注", 500));
|
||||||
|
todoTask.setDelFlag("0");
|
||||||
|
return todoTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateQuery(TodoTaskQuery query) {
|
||||||
|
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.setTaskType(normalizeOptional(query.getTaskType(), "待办类型", 64));
|
||||||
|
query.setKeyword(normalizeOptional(query.getKeyword(), "关键字", 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateIdentity(Long todoId, Long assigneeUserId) {
|
||||||
|
if (todoId == null || todoId <= 0) {
|
||||||
|
throw new ServiceException("待办任务 ID 不能为空");
|
||||||
|
}
|
||||||
|
validateUserId(assigneeUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateUserId(Long userId) {
|
||||||
|
if (userId == null || userId <= 0) {
|
||||||
|
throw new ServiceException("待办接收用户不能为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeRequired(String value, String fieldName, int maxLength) {
|
||||||
|
String safeValue = StringUtils.trim(value);
|
||||||
|
if (StringUtils.isEmpty(safeValue)) {
|
||||||
|
throw new ServiceException(fieldName + "不能为空");
|
||||||
|
}
|
||||||
|
if (safeValue.length() > maxLength) {
|
||||||
|
throw new ServiceException(fieldName + "不能超过" + maxLength + "个字符");
|
||||||
|
}
|
||||||
|
return safeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeOptional(String value, String fieldName, int maxLength) {
|
||||||
|
if (StringUtils.isEmpty(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String safeValue = value.trim();
|
||||||
|
if (safeValue.length() > maxLength) {
|
||||||
|
throw new ServiceException(fieldName + "不能超过" + maxLength + "个字符");
|
||||||
|
}
|
||||||
|
return safeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeOperator(String operatorName, Long operatorId) {
|
||||||
|
String safeOperator = StringUtils.isNotEmpty(operatorName)
|
||||||
|
? operatorName.trim()
|
||||||
|
: (operatorId == null ? SYSTEM_OPERATOR : String.valueOf(operatorId));
|
||||||
|
if (safeOperator.length() > 64) {
|
||||||
|
throw new ServiceException("操作人名称不能超过64个字符");
|
||||||
|
}
|
||||||
|
return safeOperator;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.jsowell.system.mapper.SysTodoTaskMapper">
|
||||||
|
|
||||||
|
<resultMap type="SysTodoTask" id="SysTodoTaskResult">
|
||||||
|
<id property="todoId" column="todo_id" />
|
||||||
|
<result property="taskType" column="task_type" />
|
||||||
|
<result property="title" column="title" />
|
||||||
|
<result property="summary" column="summary" />
|
||||||
|
<result property="content" column="content" />
|
||||||
|
<result property="businessType" column="business_type" />
|
||||||
|
<result property="businessId" column="business_id" />
|
||||||
|
<result property="routeName" column="route_name" />
|
||||||
|
<result property="routeParams" column="route_params" />
|
||||||
|
<result property="assigneeUserId" column="assignee_user_id" />
|
||||||
|
<result property="assigneeMerchantId" column="assignee_merchant_id" />
|
||||||
|
<result property="priority" column="priority" />
|
||||||
|
<result property="taskStatus" column="task_status" />
|
||||||
|
<result property="readStatus" column="read_status" />
|
||||||
|
<result property="readTime" column="read_time" />
|
||||||
|
<result property="dueTime" column="due_time" />
|
||||||
|
<result property="completedTime" column="completed_time" />
|
||||||
|
<result property="completedBy" column="completed_by" />
|
||||||
|
<result property="cancelReason" column="cancel_reason" />
|
||||||
|
<result property="idempotentKey" column="idempotent_key" />
|
||||||
|
<result property="activeIdempotentKey" column="active_idempotent_key" />
|
||||||
|
<result property="createBy" column="create_by" />
|
||||||
|
<result property="createTime" column="create_time" />
|
||||||
|
<result property="updateBy" column="update_by" />
|
||||||
|
<result property="updateTime" column="update_time" />
|
||||||
|
<result property="remark" column="remark" />
|
||||||
|
<result property="delFlag" column="del_flag" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="selectTodoVo">
|
||||||
|
select todo_id, task_type, title, summary, cast(content as char) as content,
|
||||||
|
business_type, business_id, route_name, route_params,
|
||||||
|
assignee_user_id, assignee_merchant_id, priority, task_status,
|
||||||
|
read_status, read_time, due_time, completed_time, completed_by,
|
||||||
|
cancel_reason, idempotent_key, active_idempotent_key,
|
||||||
|
create_by, create_time, update_by, update_time, remark, del_flag
|
||||||
|
from sys_todo_task
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectTodoById" parameterType="Long" resultMap="SysTodoTaskResult">
|
||||||
|
<include refid="selectTodoVo" />
|
||||||
|
where todo_id = #{todoId}
|
||||||
|
and del_flag = '0'
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectTodoByIdForAssignee" resultMap="SysTodoTaskResult">
|
||||||
|
<include refid="selectTodoVo" />
|
||||||
|
where todo_id = #{todoId}
|
||||||
|
and assignee_user_id = #{assigneeUserId}
|
||||||
|
and del_flag = '0'
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectActiveByIdempotentKey" parameterType="String" resultMap="SysTodoTaskResult">
|
||||||
|
<include refid="selectTodoVo" />
|
||||||
|
where active_idempotent_key = #{activeIdempotentKey}
|
||||||
|
and del_flag = '0'
|
||||||
|
limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectTodoList" parameterType="com.jsowell.system.domain.dto.TodoTaskQuery"
|
||||||
|
resultMap="SysTodoTaskResult">
|
||||||
|
<include refid="selectTodoVo" />
|
||||||
|
<where>
|
||||||
|
del_flag = '0'
|
||||||
|
and assignee_user_id = #{assigneeUserId}
|
||||||
|
<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">
|
||||||
|
<include refid="selectTodoVo" />
|
||||||
|
where assignee_user_id = #{assigneeUserId}
|
||||||
|
and task_status in ('PENDING', 'PROCESSING')
|
||||||
|
and del_flag = '0'
|
||||||
|
order by priority desc, create_time desc, todo_id desc
|
||||||
|
limit #{limit}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countActive" parameterType="Long" resultType="int">
|
||||||
|
select count(1)
|
||||||
|
from sys_todo_task
|
||||||
|
where assignee_user_id = #{assigneeUserId}
|
||||||
|
and task_status in ('PENDING', 'PROCESSING')
|
||||||
|
and del_flag = '0'
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countUnread" parameterType="Long" resultType="int">
|
||||||
|
select count(1)
|
||||||
|
from sys_todo_task
|
||||||
|
where assignee_user_id = #{assigneeUserId}
|
||||||
|
and task_status in ('PENDING', 'PROCESSING')
|
||||||
|
and read_status = '0'
|
||||||
|
and del_flag = '0'
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insertTodoTask" parameterType="SysTodoTask" useGeneratedKeys="true" keyProperty="todoId">
|
||||||
|
insert into sys_todo_task (
|
||||||
|
task_type, title, summary, content, business_type, business_id,
|
||||||
|
route_name, route_params, assignee_user_id, assignee_merchant_id,
|
||||||
|
priority, task_status, read_status, due_time, idempotent_key,
|
||||||
|
active_idempotent_key, create_by, create_time, remark, del_flag
|
||||||
|
) values (
|
||||||
|
#{taskType}, #{title}, #{summary}, #{content}, #{businessType}, #{businessId},
|
||||||
|
#{routeName}, #{routeParams}, #{assigneeUserId}, #{assigneeMerchantId},
|
||||||
|
#{priority}, #{taskStatus}, #{readStatus}, #{dueTime}, #{idempotentKey},
|
||||||
|
#{activeIdempotentKey}, #{createBy}, sysdate(), #{remark}, #{delFlag}
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="markTodoRead">
|
||||||
|
update sys_todo_task
|
||||||
|
set read_status = '1',
|
||||||
|
read_time = sysdate(),
|
||||||
|
update_by = #{updateBy},
|
||||||
|
update_time = sysdate()
|
||||||
|
where todo_id = #{todoId}
|
||||||
|
and assignee_user_id = #{assigneeUserId}
|
||||||
|
and read_status = '0'
|
||||||
|
and del_flag = '0'
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="markAllRead">
|
||||||
|
update sys_todo_task
|
||||||
|
set read_status = '1',
|
||||||
|
read_time = sysdate(),
|
||||||
|
update_by = #{updateBy},
|
||||||
|
update_time = sysdate()
|
||||||
|
where assignee_user_id = #{assigneeUserId}
|
||||||
|
and read_status = '0'
|
||||||
|
and del_flag = '0'
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="completeTodoForAssignee">
|
||||||
|
update sys_todo_task
|
||||||
|
set task_status = 'COMPLETED',
|
||||||
|
completed_time = sysdate(),
|
||||||
|
completed_by = #{completedBy},
|
||||||
|
active_idempotent_key = null,
|
||||||
|
update_by = #{updateBy},
|
||||||
|
update_time = sysdate()
|
||||||
|
where todo_id = #{todoId}
|
||||||
|
and assignee_user_id = #{assigneeUserId}
|
||||||
|
and task_status in ('PENDING', 'PROCESSING')
|
||||||
|
and del_flag = '0'
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="completeTodoByBusiness">
|
||||||
|
update sys_todo_task
|
||||||
|
set task_status = 'COMPLETED',
|
||||||
|
completed_time = sysdate(),
|
||||||
|
completed_by = #{completedBy},
|
||||||
|
active_idempotent_key = null,
|
||||||
|
update_by = #{updateBy},
|
||||||
|
update_time = sysdate()
|
||||||
|
where business_type = #{businessType}
|
||||||
|
and business_id = #{businessId}
|
||||||
|
<if test="assigneeUserId != null">
|
||||||
|
and assignee_user_id = #{assigneeUserId}
|
||||||
|
</if>
|
||||||
|
and task_status in ('PENDING', 'PROCESSING')
|
||||||
|
and del_flag = '0'
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="cancelTodoForAssignee">
|
||||||
|
update sys_todo_task
|
||||||
|
set task_status = 'CANCELLED',
|
||||||
|
cancel_reason = #{reason},
|
||||||
|
active_idempotent_key = null,
|
||||||
|
update_by = #{updateBy},
|
||||||
|
update_time = sysdate()
|
||||||
|
where todo_id = #{todoId}
|
||||||
|
and assignee_user_id = #{assigneeUserId}
|
||||||
|
and task_status in ('PENDING', 'PROCESSING')
|
||||||
|
and del_flag = '0'
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="cancelTodo">
|
||||||
|
update sys_todo_task
|
||||||
|
set task_status = 'CANCELLED',
|
||||||
|
cancel_reason = #{reason},
|
||||||
|
active_idempotent_key = null,
|
||||||
|
update_by = #{updateBy},
|
||||||
|
update_time = sysdate()
|
||||||
|
where todo_id = #{todoId}
|
||||||
|
and task_status in ('PENDING', 'PROCESSING')
|
||||||
|
and del_flag = '0'
|
||||||
|
</update>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
Reference in New Issue
Block a user