From 181d4a5b40f6217fe982988e95f5958ba8f0d3e2 Mon Sep 17 00:00:00 2001 From: jsowell <123@jsowell.com> Date: Thu, 16 Jul 2026 16:11:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BE=85=E5=8A=9E=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E9=80=9A=E7=94=A8=E9=A2=86=E5=9F=9F=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-07-16-todo-center-task-tracker.md | 10 +- .../system/constant/TodoTaskConstants.java | 44 +++ .../jsowell/system/domain/SysTodoTask.java | 47 +++ .../domain/dto/TodoTaskCreateCommand.java | 38 +++ .../system/domain/dto/TodoTaskQuery.java | 23 ++ .../system/mapper/SysTodoTaskMapper.java | 59 ++++ .../system/service/TodoTaskService.java | 39 +++ .../service/impl/TodoTaskServiceImpl.java | 286 ++++++++++++++++++ .../mapper/system/SysTodoTaskMapper.xml | 215 +++++++++++++ 9 files changed, 757 insertions(+), 4 deletions(-) create mode 100644 jsowell-system/src/main/java/com/jsowell/system/constant/TodoTaskConstants.java create mode 100644 jsowell-system/src/main/java/com/jsowell/system/domain/SysTodoTask.java create mode 100644 jsowell-system/src/main/java/com/jsowell/system/domain/dto/TodoTaskCreateCommand.java create mode 100644 jsowell-system/src/main/java/com/jsowell/system/domain/dto/TodoTaskQuery.java create mode 100644 jsowell-system/src/main/java/com/jsowell/system/mapper/SysTodoTaskMapper.java create mode 100644 jsowell-system/src/main/java/com/jsowell/system/service/TodoTaskService.java create mode 100644 jsowell-system/src/main/java/com/jsowell/system/service/impl/TodoTaskServiceImpl.java create mode 100644 jsowell-system/src/main/resources/mapper/system/SysTodoTaskMapper.xml diff --git a/docs/plan/2026-07-16-todo-center-task-tracker.md b/docs/plan/2026-07-16-todo-center-task-tracker.md index e83935fa0..47fa3de21 100644 --- a/docs/plan/2026-07-16-todo-center-task-tracker.md +++ b/docs/plan/2026-07-16-todo-center-task-tracker.md @@ -41,8 +41,8 @@ PRD 同时要求“幂等键唯一”和“任务完成后允许同一业务重 | 批次 | 范围 | 优先级 | 状态 | 验收结果 | Commit | | --- | --- | --- | --- | --- | --- | -| B01 | 任务拆分、数据表和索引 | P0 | done | SQL 结构和索引评审通过 | 本批提交 | -| B02 | 实体、命令模型、枚举、Mapper、Service | P0 | todo | - | - | +| B01 | 任务拆分、数据表和索引 | P0 | done | SQL 结构和索引评审通过 | `e65017ab2` | +| B02 | 实体、命令模型、枚举、Mapper、Service | P0 | done | 模块编译和 Mapper XML 校验通过 | 本批提交 | | B03 | 当前用户查询、已读、完成、取消接口及测试 | P0 | todo | - | - | | B04 | 前端 API、待办中心页面和路由跳转 | P0 | todo | - | - | | B05 | 首页待办卡片、数量展示和轮询刷新 | P0 | todo | - | - | @@ -72,7 +72,7 @@ PRD 同时要求“幂等键唯一”和“任务完成后允许同一业务重 - 优先级:P0 - 批次:B02 -- 状态:todo +- 状态:done - 目标:新增实体、创建命令、状态常量、Mapper 和统一 `TodoTaskService`。 - 验收标准: - 创建接口校验必要字段并设置默认状态、优先级和阅读状态。 @@ -173,4 +173,6 @@ PRD 同时要求“幂等键唯一”和“任务完成后允许同一业务重 每完成一个批次,在此追加:完成时间、验证命令、结果和 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 在下一批记录中回填。 diff --git a/jsowell-system/src/main/java/com/jsowell/system/constant/TodoTaskConstants.java b/jsowell-system/src/main/java/com/jsowell/system/constant/TodoTaskConstants.java new file mode 100644 index 000000000..e748ec4c2 --- /dev/null +++ b/jsowell-system/src/main/java/com/jsowell/system/constant/TodoTaskConstants.java @@ -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); + } +} diff --git a/jsowell-system/src/main/java/com/jsowell/system/domain/SysTodoTask.java b/jsowell-system/src/main/java/com/jsowell/system/domain/SysTodoTask.java new file mode 100644 index 000000000..a5abbb422 --- /dev/null +++ b/jsowell-system/src/main/java/com/jsowell/system/domain/SysTodoTask.java @@ -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; +} diff --git a/jsowell-system/src/main/java/com/jsowell/system/domain/dto/TodoTaskCreateCommand.java b/jsowell-system/src/main/java/com/jsowell/system/domain/dto/TodoTaskCreateCommand.java new file mode 100644 index 000000000..94b7dbfad --- /dev/null +++ b/jsowell-system/src/main/java/com/jsowell/system/domain/dto/TodoTaskCreateCommand.java @@ -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; +} diff --git a/jsowell-system/src/main/java/com/jsowell/system/domain/dto/TodoTaskQuery.java b/jsowell-system/src/main/java/com/jsowell/system/domain/dto/TodoTaskQuery.java new file mode 100644 index 000000000..77bd9508e --- /dev/null +++ b/jsowell-system/src/main/java/com/jsowell/system/domain/dto/TodoTaskQuery.java @@ -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; +} diff --git a/jsowell-system/src/main/java/com/jsowell/system/mapper/SysTodoTaskMapper.java b/jsowell-system/src/main/java/com/jsowell/system/mapper/SysTodoTaskMapper.java new file mode 100644 index 000000000..0692476c3 --- /dev/null +++ b/jsowell-system/src/main/java/com/jsowell/system/mapper/SysTodoTaskMapper.java @@ -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 selectTodoList(TodoTaskQuery query); + + List 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); +} diff --git a/jsowell-system/src/main/java/com/jsowell/system/service/TodoTaskService.java b/jsowell-system/src/main/java/com/jsowell/system/service/TodoTaskService.java new file mode 100644 index 000000000..8cb3329c9 --- /dev/null +++ b/jsowell-system/src/main/java/com/jsowell/system/service/TodoTaskService.java @@ -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 listForAssignee(TodoTaskQuery query, Long assigneeUserId); + + List 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); +} diff --git a/jsowell-system/src/main/java/com/jsowell/system/service/impl/TodoTaskServiceImpl.java b/jsowell-system/src/main/java/com/jsowell/system/service/impl/TodoTaskServiceImpl.java new file mode 100644 index 000000000..142b2f307 --- /dev/null +++ b/jsowell-system/src/main/java/com/jsowell/system/service/impl/TodoTaskServiceImpl.java @@ -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 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 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; + } +} diff --git a/jsowell-system/src/main/resources/mapper/system/SysTodoTaskMapper.xml b/jsowell-system/src/main/resources/mapper/system/SysTodoTaskMapper.xml new file mode 100644 index 000000000..35aed1509 --- /dev/null +++ b/jsowell-system/src/main/resources/mapper/system/SysTodoTaskMapper.xml @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + + + + + + + + + + + + + + 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} + ) + + + + 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 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 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 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} + + and assignee_user_id = #{assigneeUserId} + + and task_status in ('PENDING', 'PROCESSING') + and del_flag = '0' + + + + 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 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' + + +