fix(schedule): correct project binding and atomic lock flow

This commit is contained in:
2026-02-15 16:49:54 +08:00
parent 7e2e9bb0bc
commit 72ceb694a6
5 changed files with 127 additions and 69 deletions
@@ -63,7 +63,7 @@ public class MartialScheduleArrangeController extends BladeController {
return R.data(result);
} catch (Exception e) {
log.error("获取编排结果失败, competitionId: {}", competitionId, e);
return R.fail("获取编排结果失败: " + e.getMessage());
return R.fail("获取编排结果失败");
}
}
@@ -78,7 +78,7 @@ public class MartialScheduleArrangeController extends BladeController {
return success ? R.success("草稿保存成功") : R.fail("草稿保存失败");
} catch (Exception e) {
log.error("保存编排草稿失败", e);
return R.fail("保存编排草稿失败: " + e.getMessage());
return R.fail("保存编排草稿失败");
}
}
@@ -91,17 +91,11 @@ public class MartialScheduleArrangeController extends BladeController {
try {
BladeUser user = AuthUtil.getUser();
String userId = user != null ? user.getUserName() : "system";
boolean success = scheduleService.saveAndLockSchedule(dto.getCompetitionId());
if (success) {
scheduleArrangeService.saveAndLock(dto.getCompetitionId(), userId);
return R.success("编排已完成并锁定");
} else {
return R.fail("编排锁定失败");
}
boolean success = scheduleService.saveDraftAndLockSchedule(dto, userId);
return success ? R.success("编排已完成并锁定") : R.fail("编排锁定失败");
} catch (Exception e) {
log.error("保存并锁定编排失败", e);
return R.fail("保存并锁定编排失败: " + e.getMessage());
return R.fail("保存并锁定编排失败");
}
}
@@ -117,7 +111,7 @@ public class MartialScheduleArrangeController extends BladeController {
return R.success("自动编排完成");
} catch (Exception e) {
log.error("自动编排失败", e);
return R.fail("自动编排失败: " + e.getMessage());
return R.fail("自动编排失败");
}
}
@@ -132,7 +126,7 @@ public class MartialScheduleArrangeController extends BladeController {
return success ? R.success("分组移动成功") : R.fail("分组移动失败");
} catch (Exception e) {
log.error("移动分组失败", e);
return R.fail("移动分组失败: " + e.getMessage());
return R.fail("移动分组失败");
}
}
@@ -151,7 +145,7 @@ public class MartialScheduleArrangeController extends BladeController {
return R.data(data);
} catch (Exception e) {
log.error("获取调度数据失败", e);
return R.fail("获取调度数据失败: " + e.getMessage());
return R.fail("获取调度数据失败");
}
}
@@ -166,7 +160,7 @@ public class MartialScheduleArrangeController extends BladeController {
return success ? R.success("顺序调整成功") : R.fail("顺序调整失败");
} catch (Exception e) {
log.error("调整顺序失败", e);
return R.fail("调整顺序失败: " + e.getMessage());
return R.fail("调整顺序失败");
}
}
@@ -181,7 +175,7 @@ public class MartialScheduleArrangeController extends BladeController {
return success ? R.success("调度保存成功") : R.fail("调度保存失败");
} catch (Exception e) {
log.error("保存调度失败", e);
return R.fail("保存调度失败: " + e.getMessage());
return R.fail("保存调度失败");
}
}
@@ -198,7 +192,7 @@ public class MartialScheduleArrangeController extends BladeController {
return success ? R.success("状态更新成功") : R.fail("状态更新失败");
} catch (Exception e) {
log.error("更新签到状态失败", e);
return R.fail("更新签到状态失败: " + e.getMessage());
return R.fail("更新签到状态失败");
}
}
@@ -6,6 +6,7 @@
<select id="selectScheduleGroupDetails" resultType="org.springblade.modules.martial.pojo.vo.ScheduleGroupDetailVO">
SELECT
g.id AS groupId,
g.project_id AS projectId,
g.group_name AS groupName,
g.category AS category,
g.project_type AS projectType,
@@ -15,6 +15,7 @@ public class ScheduleGroupDetailVO implements Serializable {
// === 分组信息 ===
private Long groupId;
private Long projectId;
private String groupName;
private String category;
private Integer projectType;
@@ -25,6 +25,8 @@ public interface IMartialScheduleService extends IService<MartialSchedule> {
boolean saveAndLockSchedule(Long competitionId);
boolean saveDraftAndLockSchedule(SaveScheduleDraftDTO dto, String userId);
boolean moveScheduleGroup(MoveScheduleGroupDTO dto);
org.springblade.modules.martial.pojo.vo.DispatchDataVO getDispatchData(Long competitionId, Long venueId, Integer timeSlotIndex);
@@ -2,7 +2,6 @@ package org.springblade.modules.martial.service.impl;
import org.springblade.modules.martial.service.IScheduleExportService;
import org.springblade.modules.martial.service.IScheduleQueryService;
import org.springblade.modules.martial.service.IScheduleArrangeService;
import org.springblade.modules.martial.service.IScheduleDispatchService;
import org.springblade.modules.martial.service.IScheduleStatusService;
@@ -62,7 +61,7 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
private IScheduleQueryService scheduleQueryService;
@Autowired
private IScheduleArrangeService scheduleArrangeService;
private IMartialScheduleArrangeService martialScheduleArrangeService;
@Autowired
private IScheduleDispatchService scheduleDispatchService;
@@ -177,6 +176,7 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
CompetitionGroupDTO groupDTO = new CompetitionGroupDTO();
groupDTO.setId(firstDetail.getGroupId());
groupDTO.setProjectId(firstDetail.getProjectId());
groupDTO.setTitle(firstDetail.getGroupName());
groupDTO.setCode(firstDetail.getCategory());
@@ -468,51 +468,44 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveDraftSchedule(SaveScheduleDraftDTO dto) {
if (dto.getCompetitionGroups() == null || dto.getCompetitionGroups().isEmpty()) {
if (dto == null || dto.getCompetitionId() == null || dto.getCompetitionGroups() == null || dto.getCompetitionGroups().isEmpty()) {
return false;
}
List<MartialScheduleGroup> existingGroups = scheduleGroupMapper.selectList(
new QueryWrapper<MartialScheduleGroup>()
.eq("competition_id", dto.getCompetitionId())
.eq("is_deleted", 0)
.orderByAsc("id")
);
Set<Long> occupiedGroupIds = dto.getCompetitionGroups().stream()
.map(CompetitionGroupDTO::getId)
.filter(Objects::nonNull)
.filter(id -> id > 0)
.collect(Collectors.toCollection(HashSet::new));
Map<Long, Long> tempGroupIdMappings = new HashMap<>();
for (CompetitionGroupDTO groupDTO : dto.getCompetitionGroups()) {
// 1. 更新或创建编排明细
Long groupId = groupDTO.getId();
Long realGroupId = groupId;
// Handle negative ID (draft data from auto-arrange)
// 负数分组ID表示前端临时分组,优先使用DTO中的projectId,不再从code后缀推断
if (groupId != null && groupId < 0) {
Long projectId = null;
if (groupDTO.getCode() != null && groupDTO.getCode().contains("-P")) {
try {
String suffix = groupDTO.getCode().substring(groupDTO.getCode().lastIndexOf("-P") + 2);
projectId = Long.parseLong(suffix);
} catch (Exception e) {
log.warn("Failed to parse projectId from code: {}", groupDTO.getCode());
}
realGroupId = tempGroupIdMappings.get(groupId);
if (realGroupId == null) {
Long projectId = resolveProjectId(groupDTO);
if (projectId == null) {
throw new IllegalArgumentException("临时分组缺少projectId,无法保存");
}
MartialScheduleGroup existingGroup = null;
if (projectId != null) {
existingGroup = scheduleGroupMapper.selectOne(
new QueryWrapper<MartialScheduleGroup>()
.eq("competition_id", dto.getCompetitionId())
.eq("project_id", projectId)
.eq("is_deleted", 0)
.last("LIMIT 1")
);
}
// Determine project type from DTO
Integer projectType = null;
if ("集体".equals(groupDTO.getType())) {
projectType = 2;
} else if ("单人".equals(groupDTO.getType())) {
projectType = 1;
}
// Calculate participant/team counts
Integer projectType = resolveProjectType(groupDTO.getType());
int participantCount = groupDTO.getParticipants() != null ? groupDTO.getParticipants().size() : 0;
MartialScheduleGroup existingGroup = findReusableGroup(existingGroups, occupiedGroupIds, projectId, groupDTO.getCode());
if (existingGroup != null) {
realGroupId = existingGroup.getId();
existingGroup.setProjectId(projectId);
existingGroup.setGroupName(groupDTO.getTitle());
existingGroup.setProjectType(projectType);
existingGroup.setTotalParticipants(participantCount);
@@ -521,7 +514,7 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
scheduleGroupMapper.updateById(existingGroup);
} else {
MartialScheduleGroup newGroup = new MartialScheduleGroup();
newGroup.setCompetitionId(Long.valueOf(dto.getCompetitionId()));
newGroup.setCompetitionId(dto.getCompetitionId());
newGroup.setProjectId(projectId);
newGroup.setGroupName(groupDTO.getTitle());
newGroup.setProjectType(projectType);
@@ -530,7 +523,15 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
newGroup.setCategory(groupDTO.getCode());
scheduleGroupMapper.insert(newGroup);
realGroupId = newGroup.getId();
existingGroups.add(newGroup);
}
tempGroupIdMappings.put(groupId, realGroupId);
occupiedGroupIds.add(realGroupId);
}
}
if (realGroupId == null) {
throw new IllegalArgumentException("分组ID不能为空");
}
MartialScheduleDetail detail = scheduleDetailMapper.selectOne(
@@ -692,6 +693,65 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveDraftAndLockSchedule(SaveScheduleDraftDTO dto, String userId) {
if (dto == null || dto.getCompetitionId() == null) {
throw new IllegalArgumentException("competitionId不能为空");
}
boolean hasDraftGroups = dto.getCompetitionGroups() != null && !dto.getCompetitionGroups().isEmpty();
if (hasDraftGroups && !saveDraftSchedule(dto)) {
throw new IllegalStateException("保存编排草稿失败");
}
if (!saveAndLockSchedule(dto.getCompetitionId())) {
throw new IllegalStateException("保存并锁定编排失败");
}
martialScheduleArrangeService.saveAndLock(dto.getCompetitionId(), userId);
return true;
}
private Long resolveProjectId(CompetitionGroupDTO groupDTO) {
if (groupDTO.getProjectId() != null) {
return groupDTO.getProjectId();
}
Long groupId = groupDTO.getId();
if (groupId != null && groupId < 0) {
Long fallbackProjectId = -groupId;
log.warn("临时分组缺少projectId,使用groupId反推projectId。groupId: {}, fallbackProjectId: {}", groupId, fallbackProjectId);
return fallbackProjectId;
}
return null;
}
private Integer resolveProjectType(String type) {
if ("集体".equals(type)) {
return 2;
}
if ("单人".equals(type)) {
return 1;
}
return null;
}
private MartialScheduleGroup findReusableGroup(List<MartialScheduleGroup> existingGroups, Set<Long> occupiedGroupIds,
Long projectId, String category) {
MartialScheduleGroup categoryMatchedGroup = existingGroups.stream()
.filter(group -> !occupiedGroupIds.contains(group.getId()))
.filter(group -> Objects.equals(group.getProjectId(), projectId))
.filter(group -> Objects.equals(group.getCategory(), category))
.findFirst()
.orElse(null);
if (categoryMatchedGroup != null) {
return categoryMatchedGroup;
}
return existingGroups.stream()
.filter(group -> !occupiedGroupIds.contains(group.getId()))
.filter(group -> Objects.equals(group.getProjectId(), projectId))
.findFirst()
.orElse(null);
}
/**
* 根据时间段字符串计算 timeSlotIndex
* 按照时间从早到晚分配索引: 08:30=0, 13:30=1, 其他时间按字符串顺序排序