fix: prevent queue bypass when unlock_status missing

This commit is contained in:
Your Name 2026-03-12 14:34:43 +08:00
parent 4f9e17a1fc
commit 2331aec8c0
2 changed files with 211 additions and 67 deletions

View File

@ -16,21 +16,38 @@ public interface TaskUnlockStatusMapper extends BaseMapper<TaskUnlockStatus> {
/**
* 获取指定部门已解锁的任务ID列表
*/
@Select("SELECT task_item_id FROM task_unlock_status WHERE dept_id = #{deptId} AND is_unlocked = 1")
@Select("SELECT tus.task_item_id FROM task_unlock_status tus " +
"INNER JOIN task_item ti ON tus.task_item_id = ti.id " +
"INNER JOIN task t ON ti.task_id = t.id " +
"WHERE tus.dept_id = #{deptId} AND tus.is_unlocked = 1 " +
" AND ti.status = 'pending' AND ti.is_locked = 0 " +
" AND t.deleted = 0 AND t.approval_status = 'approved' " +
" AND (t.release_type IS NULL OR t.release_type != 'assigned')")
List<Long> selectUnlockedTaskItemIds(@Param("deptId") Long deptId);
/**
* 获取指定部门未解锁的任务ID列表(按发布日期排序)
*/
@Select("SELECT task_item_id FROM task_unlock_status " +
"WHERE dept_id = #{deptId} AND is_unlocked = 0 " +
"ORDER BY publish_date ASC, id ASC")
@Select("SELECT tus.task_item_id FROM task_unlock_status tus " +
"INNER JOIN task_item ti ON tus.task_item_id = ti.id " +
"INNER JOIN task t ON ti.task_id = t.id " +
"WHERE tus.dept_id = #{deptId} AND tus.is_unlocked = 0 " +
" AND ti.status = 'pending' AND ti.is_locked = 0 " +
" AND t.deleted = 0 AND t.approval_status = 'approved' " +
" AND (t.release_type IS NULL OR t.release_type != 'assigned') " +
"ORDER BY tus.publish_date ASC, tus.id ASC")
List<Long> selectQueuedTaskItemIds(@Param("deptId") Long deptId);
/**
* 获取指定部门未解锁任务总数
*/
@Select("SELECT COUNT(*) FROM task_unlock_status WHERE dept_id = #{deptId} AND is_unlocked = 0")
@Select("SELECT COUNT(*) FROM task_unlock_status tus " +
"INNER JOIN task_item ti ON tus.task_item_id = ti.id " +
"INNER JOIN task t ON ti.task_id = t.id " +
"WHERE tus.dept_id = #{deptId} AND tus.is_unlocked = 0 " +
" AND ti.status = 'pending' AND ti.is_locked = 0 " +
" AND t.deleted = 0 AND t.approval_status = 'approved' " +
" AND (t.release_type IS NULL OR t.release_type != 'assigned')")
int countQueuedTasks(@Param("deptId") Long deptId);
/**
@ -38,7 +55,11 @@ public interface TaskUnlockStatusMapper extends BaseMapper<TaskUnlockStatus> {
*/
@Select("SELECT COUNT(*) FROM task_unlock_status tus " +
"INNER JOIN task_item ti ON tus.task_item_id = ti.id " +
"WHERE tus.dept_id = #{deptId} AND tus.is_unlocked = 1 AND ti.status = 'pending'")
"INNER JOIN task t ON ti.task_id = t.id " +
"WHERE tus.dept_id = #{deptId} AND tus.is_unlocked = 1 " +
" AND ti.status = 'pending' AND ti.is_locked = 0 " +
" AND t.deleted = 0 AND t.approval_status = 'approved' " +
" AND (t.release_type IS NULL OR t.release_type != 'assigned')")
int countUnlockedPendingTasks(@Param("deptId") Long deptId);
/**
@ -46,7 +67,11 @@ public interface TaskUnlockStatusMapper extends BaseMapper<TaskUnlockStatus> {
*/
@Select("SELECT COUNT(*) FROM task_unlock_status tus " +
"INNER JOIN task_item ti ON tus.task_item_id = ti.id " +
"WHERE tus.dept_id = #{deptId} AND tus.is_unlocked = 0 AND ti.status = 'pending'")
"INNER JOIN task t ON ti.task_id = t.id " +
"WHERE tus.dept_id = #{deptId} AND tus.is_unlocked = 0 " +
" AND ti.status = 'pending' AND ti.is_locked = 0 " +
" AND t.deleted = 0 AND t.approval_status = 'approved' " +
" AND (t.release_type IS NULL OR t.release_type != 'assigned')")
int countQueuedPendingTasks(@Param("deptId") Long deptId);
/**
@ -65,9 +90,14 @@ public interface TaskUnlockStatusMapper extends BaseMapper<TaskUnlockStatus> {
/**
* 获取指定部门按发布日期分组的未解锁任务统计
*/
@Select("SELECT publish_date, COUNT(*) as count FROM task_unlock_status " +
"WHERE dept_id = #{deptId} AND is_unlocked = 0 " +
"GROUP BY publish_date ORDER BY publish_date ASC")
@Select("SELECT tus.publish_date, COUNT(*) as count FROM task_unlock_status tus " +
"INNER JOIN task_item ti ON tus.task_item_id = ti.id " +
"INNER JOIN task t ON ti.task_id = t.id " +
"WHERE tus.dept_id = #{deptId} AND tus.is_unlocked = 0 " +
" AND ti.status = 'pending' AND ti.is_locked = 0 " +
" AND t.deleted = 0 AND t.approval_status = 'approved' " +
" AND (t.release_type IS NULL OR t.release_type != 'assigned') " +
"GROUP BY tus.publish_date ORDER BY tus.publish_date ASC")
@Results({
@Result(column = "publish_date", property = "publishDate"),
@Result(column = "count", property = "count")
@ -77,20 +107,30 @@ public interface TaskUnlockStatusMapper extends BaseMapper<TaskUnlockStatus> {
/**
* 获取需要解锁的任务ID列表(按日期顺序,限制数量)
*/
@Select("SELECT task_item_id FROM task_unlock_status " +
"WHERE dept_id = #{deptId} AND is_unlocked = 0 " +
"ORDER BY publish_date ASC, id ASC " +
@Select("SELECT tus.task_item_id FROM task_unlock_status tus " +
"INNER JOIN task_item ti ON tus.task_item_id = ti.id " +
"INNER JOIN task t ON ti.task_id = t.id " +
"WHERE tus.dept_id = #{deptId} AND tus.is_unlocked = 0 " +
" AND ti.status = 'pending' AND ti.is_locked = 0 " +
" AND t.deleted = 0 AND t.approval_status = 'approved' " +
" AND (t.release_type IS NULL OR t.release_type != 'assigned') " +
"ORDER BY tus.publish_date ASC, tus.id ASC " +
"LIMIT #{limit}")
List<Long> selectTasksToUnlock(@Param("deptId") Long deptId, @Param("limit") int limit);
/**
* 计算任务在队列中的位置
*/
@Select("SELECT COUNT(*) + 1 FROM task_unlock_status " +
"WHERE dept_id = #{deptId} AND is_unlocked = 0 " +
"AND (publish_date < (SELECT publish_date FROM task_unlock_status WHERE task_item_id = #{taskItemId} AND dept_id = #{deptId}) " +
" OR (publish_date = (SELECT publish_date FROM task_unlock_status WHERE task_item_id = #{taskItemId} AND dept_id = #{deptId}) " +
" AND id < (SELECT id FROM task_unlock_status WHERE task_item_id = #{taskItemId} AND dept_id = #{deptId})))")
@Select("SELECT COUNT(*) + 1 FROM task_unlock_status tus " +
"INNER JOIN task_item ti ON tus.task_item_id = ti.id " +
"INNER JOIN task t ON ti.task_id = t.id " +
"WHERE tus.dept_id = #{deptId} AND tus.is_unlocked = 0 " +
" AND ti.status = 'pending' AND ti.is_locked = 0 " +
" AND t.deleted = 0 AND t.approval_status = 'approved' " +
" AND (t.release_type IS NULL OR t.release_type != 'assigned') " +
" AND (tus.publish_date < (SELECT publish_date FROM task_unlock_status WHERE task_item_id = #{taskItemId} AND dept_id = #{deptId} LIMIT 1) " +
" OR (tus.publish_date = (SELECT publish_date FROM task_unlock_status WHERE task_item_id = #{taskItemId} AND dept_id = #{deptId} LIMIT 1) " +
" AND tus.id < (SELECT id FROM task_unlock_status WHERE task_item_id = #{taskItemId} AND dept_id = #{deptId} LIMIT 1)))")
int calculateQueuePosition(@Param("taskItemId") Long taskItemId, @Param("deptId") Long deptId);
/**
@ -99,6 +139,21 @@ public interface TaskUnlockStatusMapper extends BaseMapper<TaskUnlockStatus> {
@Select("SELECT is_unlocked FROM task_unlock_status WHERE task_item_id = #{taskItemId} AND dept_id = #{deptId}")
Integer isUnlocked(@Param("taskItemId") Long taskItemId, @Param("deptId") Long deptId);
/**
* 写入排队记录(若不存在则插入)
* <p>用于修复历史数据缺失导致的“新任务绕过排队”问题</p>
*/
@Insert("INSERT INTO task_unlock_status (task_item_id, dept_id, publish_date, is_unlocked, unlock_time, create_time, update_time) " +
"SELECT #{taskItemId}, #{deptId}, #{publishDate}, #{isUnlocked}, " +
" CASE WHEN #{isUnlocked} = 1 THEN NOW() ELSE NULL END, " +
" NOW(), NOW() " +
"FROM DUAL " +
"WHERE NOT EXISTS (SELECT 1 FROM task_unlock_status WHERE task_item_id = #{taskItemId} AND dept_id = #{deptId})")
int insertIfNotExists(@Param("taskItemId") Long taskItemId,
@Param("deptId") Long deptId,
@Param("publishDate") LocalDate publishDate,
@Param("isUnlocked") Integer isUnlocked);
/**
* 删除任务的解锁状态(任务取消/删除时调用)
*/

View File

@ -423,42 +423,30 @@ public class TaskUnlockService {
return;
}
LocalDate publishDate = task.getCreateTime().toLocalDate();
int addedCount = 0;
LocalDate publishDate = task.getCreateTime() != null ? task.getCreateTime().toLocalDate() : LocalDate.now();
int addedRows = 0;
for (TaskItem item : items) {
// 获取目标接单部门
Long deptId = getTargetDeptId(item, task);
if (deptId == null) {
if (item.getId() == null) {
continue;
}
List<Long> deptIds = resolveTargetDeptIds(item, task);
if (deptIds.isEmpty()) {
log.warn("子任务 {} 无法确定目标部门,跳过入队", item.getItemNo());
continue;
}
// 检查是否已存在
TaskUnlockStatus existing = unlockStatusMapper.selectOne(
new LambdaQueryWrapper<TaskUnlockStatus>()
.eq(TaskUnlockStatus::getTaskItemId, item.getId())
.eq(TaskUnlockStatus::getDeptId, deptId)
);
if (existing != null) {
continue; // 已存在,跳过
for (Long deptId : deptIds) {
if (deptId == null) {
continue;
}
addedRows += unlockStatusMapper.insertIfNotExists(item.getId(), deptId, publishDate, 0);
}
// 创建排队记录
TaskUnlockStatus unlockStatus = new TaskUnlockStatus();
unlockStatus.setTaskItemId(item.getId());
unlockStatus.setDeptId(deptId);
unlockStatus.setPublishDate(publishDate);
unlockStatus.setIsUnlocked(0); // 未解锁
unlockStatus.setCreateTime(LocalDateTime.now());
unlockStatusMapper.insert(unlockStatus);
addedCount++;
}
if (addedCount > 0) {
log.info("任务 {} 的 {} 个子任务已加入排队池", task.getTaskNo(), addedCount);
if (addedRows > 0) {
log.info("任务 {} 已写入 {} 条排队记录", task.getTaskNo(), addedRows);
}
}
@ -544,10 +532,17 @@ public class TaskUnlockService {
Integer isUnlocked = unlockStatusMapper.isUnlocked(item.getId(), deptId);
if (isUnlocked == null) {
// 没有记录,视为已解锁(兼容旧数据)
item.setUnlockStatus("unlocked");
item.setCanGrab(true);
return;
// 没有记录:补全排队记录,避免“新任务绕过排队”
LocalDate publishDate = item.getCreateTime() != null ? item.getCreateTime().toLocalDate() : LocalDate.now();
unlockStatusMapper.insertIfNotExists(item.getId(), deptId, publishDate, 0);
isUnlocked = unlockStatusMapper.isUnlocked(item.getId(), deptId);
// 兜底:依然查不到则按排队中处理
if (isUnlocked == null) {
item.setUnlockStatus("queuing");
item.setCanGrab(false);
return;
}
}
if (isUnlocked == 1) {
@ -611,37 +606,131 @@ public class TaskUnlockService {
// 检查解锁状态
Integer isUnlocked = unlockStatusMapper.isUnlocked(taskItemId, deptId);
// 没有记录或已解锁 = 可抢
return isUnlocked == null || isUnlocked == 1;
if (isUnlocked == null) {
// 没有记录:补全排队记录,默认排队中(不可抢)
ensureUnlockStatusExists(taskItemId, deptId);
return false;
}
return isUnlocked == 1;
}
// ==================== 辅助方法 ====================
/**
* 获取任务的目标接单部门ID
* 补全排队记录(仅补全当前部门维度)
* <p>用于修复历史数据缺失导致的绕过排队与不可解锁问题</p>
*/
private Long getTargetDeptId(TaskItem item, Task task) {
// 优先使用子任务的接单部门
if (item.getReceiverDeptId() != null) {
return item.getReceiverDeptId();
private void ensureUnlockStatusExists(Long taskItemId, Long deptId) {
if (taskItemId == null || deptId == null) {
return;
}
// 从任务类型获取
if (task.getDemandTypeId() != null) {
DemandType demandType = demandTypeMapper.selectById(task.getDemandTypeId());
if (demandType != null && demandType.getAllowedDeptIds() != null) {
String[] deptIds = demandType.getAllowedDeptIds().split(",");
if (deptIds.length > 0) {
try {
return Long.parseLong(deptIds[0].trim());
} catch (NumberFormatException e) {
// 忽略
LocalDate publishDate = LocalDate.now();
try {
TaskItem item = taskItemMapper.selectById(taskItemId);
if (item != null && item.getCreateTime() != null) {
publishDate = item.getCreateTime().toLocalDate();
}
if (item != null && item.getTaskId() != null) {
Task task = taskMapper.selectById(item.getTaskId());
if (task != null && task.getCreateTime() != null) {
publishDate = task.getCreateTime().toLocalDate();
}
}
} catch (Exception e) {
log.warn("补全排队记录时读取任务信息失败: taskItemId={}, deptId={}, error={}", taskItemId, deptId, e.getMessage());
}
try {
unlockStatusMapper.insertIfNotExists(taskItemId, deptId, publishDate, 0);
} catch (Exception e) {
log.warn("补全排队记录失败: taskItemId={}, deptId={}, error={}", taskItemId, deptId, e.getMessage());
}
}
/**
* 获取任务的目标接单部门ID列表(支持多部门 & 三级分类继承)
*/
private List<Long> resolveTargetDeptIds(TaskItem item, Task task) {
if (item == null) {
return Collections.emptyList();
}
// 优先使用子任务的接单部门(明确指定)
if (item.getReceiverDeptId() != null) {
return Collections.singletonList(item.getReceiverDeptId());
}
// 优先使用子任务类型配置(流水线任务主任务 demandTypeId 常为空)
Long taskTypeId = item.getTaskTypeId();
if (taskTypeId == null && task != null) {
taskTypeId = task.getDemandTypeId() != null ? task.getDemandTypeId() : task.getTaskTypeId();
}
String allowedDeptIds = null;
if (taskTypeId != null) {
DemandType demandType = demandTypeMapper.selectById(taskTypeId);
if (demandType != null) {
allowedDeptIds = demandType.getAllowedDeptIds();
if ((allowedDeptIds == null || allowedDeptIds.trim().isEmpty()) && demandType.getParentId() != null) {
DemandType parentType = demandTypeMapper.selectById(demandType.getParentId());
if (parentType != null) {
allowedDeptIds = parentType.getAllowedDeptIds();
}
}
}
}
return null;
List<Long> parsedDeptIds = parseDeptIds(allowedDeptIds);
if (!parsedDeptIds.isEmpty()) {
return parsedDeptIds;
}
// 未配置则视为所有接单部门可接:写入所有接单部门队列
List<Department> receivingDepts = departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getDeptType, "receiving")
.eq(Department::getStatus, 1)
);
if (receivingDepts == null || receivingDepts.isEmpty()) {
return Collections.emptyList();
}
List<Long> deptIds = new ArrayList<>();
for (Department dept : receivingDepts) {
if (dept != null && dept.getId() != null) {
deptIds.add(dept.getId());
}
}
return deptIds;
}
private List<Long> parseDeptIds(String allowedDeptIds) {
if (allowedDeptIds == null || allowedDeptIds.trim().isEmpty()) {
return Collections.emptyList();
}
String[] parts = allowedDeptIds.split(",");
List<Long> result = new ArrayList<>();
Set<Long> seen = new HashSet<>();
for (String part : parts) {
if (part == null) {
continue;
}
String s = part.trim();
if (s.isEmpty()) {
continue;
}
try {
Long id = Long.parseLong(s);
if (seen.add(id)) {
result.add(id);
}
} catch (NumberFormatException e) {
// 忽略
}
}
return result;
}
/**